SQL 테스트
Part 1 - 데이터베이스 연결과 스키마
이번 연습문제의 목표는 데이터베이스 연결을 할 수 있고, 주어진 스키마를 구현할 수 있도록 SQL을 작성해야 합니다.
Bare minimum requirement
Part_1 테스트를 전부 통과하십시오.
Part1_Test.java 파일을 통해 테스트를 실행할 수 있습니다.
Getting Started
1. repository 주소에서 fork 및 clone 후 코드를 작성합니다.
- IntelliJ를 실행합니다.
- 열기를 클릭한 이후, 다운로드한 폴더를 클릭하고 Open버튼을 클릭합니다.
- 신뢰할 수 있는 프로젝트를 클릭합니다.
2. 본인의 MySQL 정보를 입력해 주세요.
script/Properties.java 파일을 확인하고 내 정보를 수정해 주세요.
- learnSQL이라는 이름으로 데이터베이스를 만드세요. 앞으로 이 데이터베이스를 사용합니다.
3. 연습문제 디렉토리
/lib
- /lib/FactoryService.java
- FactoryService는 여러분들이 데이터베이스에 연결하여, 원하는 SQL을 작성하여 결과를 확인하는 것과 별개로 TEST가 동작할 수 있도록 도와주는 Class입니다. 구체적으로 해당 연습문제의 동작이 궁금하신 분들만 참고해 주시면 됩니다.
- /lib/Mysql.java
- Mysql 서비스에 접속하고 쿼리를 보내는 작업과 자원을 종료하는 동작을 도와주는 Class입니다.
/migrations
- /schema.sql
- 이번 연습문제의 핵심이자, 꼭 작성해야 하는 부분입니다. 원하는 테이블과 열을 생성할 수 있어야 합니다.
/model
- FactoryService가 사용하는 별도의 객체입니다. 해당 객체를 사용하여, 테이블을 조작하게 됩니다.
- DTO, DAO에 대한 내용은 Section3에서 학습하게 됩니다. 해당 코드를 이해하지 않아도 됩니다.
/script
- 각 파트에 맞게 SQL STATEMENT를 채워 넣어주세요. 원하는 것을 확인하고, 그에 맞는 SQL을 작성해야 합니다.
그 외 디렉토리
.gitignore 혹은 build.gradle 와 같은 부분은 코드를 보거나 검색을 통해서 이해하실 수 있었으면 합니다.
4. migrations/schema.sql을 작성해 주세요.
Users 테이블
Boards 테이블
Posts 테이블
Comments 테이블
Tags 테이블
Post_Tags 테이블
- 위와 같은 테이블과 열이 필요합니다.
- PK는 auto increment 이여야 합니다.
- FK를 제외하고는 모두 NOT NULL 이여야 합니다.
- created_at과 같은 날짜/시간 정보는 timestamp이면서 default current_timestamp입니다.
[참고4-1] 모든 테스트케이스가 실행되기 전, schema.sql 파일이 실행되고, 끝난 후, database가 삭제된 후 새로 생성됩니다.
lib/FactoryService.java의 setup 메서드를 참고해 주세요.
[참고4-2] Schema Visualizer
스키마를 디자인하기 위해 다양한 외부 도구를 사용할 수 있습니다.
- 연필과 펜을 이용한 A4용지 및 화이트보드
- DBdiagram
- WWW SQL Designer
- MYSQL Workbench
5. script/part1.java를 작성하고, Part1_Test의 모든 테스트를 통과하도록 노력하세요.
part1.java의 요구사항을 확인하고, FILL IN THIS를 채워주세요.
[참고5-1]
현재 테이블의 결과값을 List와 Map을 활용하여 터미널에 출력하고 있습니다. 실제 Mysql에서 보여주는 결과와 형식이 다를 수 있습니다.
CREATE TABLE Users (
user_id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE Boards (
board_id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT
);
CREATE TABLE Posts (
post_id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
user_id BIGINT,
board_id BIGINT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (board_id) REFERENCES Boards(board_id)
);
CREATE TABLE Comments (
comment_id BIGINT AUTO_INCREMENT PRIMARY KEY,
content TEXT NOT NULL,
user_id BIGINT,
post_id BIGINT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (post_id) REFERENCES Posts(post_id)
);
CREATE TABLE Tags (
tag_id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE Post_Tags (
post_id BIGINT NOT NULL,
tag_id BIGINT NOT NULL,
FOREIGN KEY (post_id) REFERENCES Posts(post_id),
FOREIGN KEY (tag_id) REFERENCES Tags(tag_id)
);
Part 2 - 기본 , 심화 SQL
Part 2의 목표는 Part 1에서 구성한 스키마에서 데이터를 추가하거나, 불러올 수 있도록 SQL을 작성하는 것을 요구합니다. Learn SQL에서 연습했던 것을 Java 환경에서 다시 한번 확인해 볼 수 있습니다.
Bare minimum requirement
Part 2 테스트를 전부 통과하십시오.
Part2_Test.java 파일을 통해 테스트를 실행할 수 있습니다.
Getting Started
script/part2.java를 작성하고, Part 2의 모든 테스트를 통과하도록 노력하세요.
아래 사항을 참고하여 진행해 주세요.
- Part 1의 테스트케이스 역시 통과되어야 합니다.
- 이번 Part에서는 schema.sql 파일의 변경은 필요하지 않습니다. Learn SQL에서 연습하고 학습한 SQL을 충분히 활용해 보세요.
- Part 2의 테스트에서는 아래 표와 같이 데이터가 존재합니다. 이는 아래의 도표가 아닌, 테스트케이스를 살펴보아도 알 수 있습니다. (lib/FactoryService.java part2_setup 메서드를 참고해 주세요.)
users 테이블 데이터
boards 테이블 데이터
posts 테이블 데이터
comments 테이블 데이터
tags 테이블 데이터
post_tags 테이블 데이터
schema.sql
CREATE TABLE `Tags` (
`tag_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE `Users` (
`user_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(255) NOT NULL UNIQUE,
`email` VARCHAR(255) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE `Boards` (
`board_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` TEXT
);
CREATE TABLE `Posts` (
`post_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` TEXT NOT NULL,
`user_id` BIGINT,
`board_id` BIGINT,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `Users` (`user_id`),
FOREIGN KEY (`board_id`) REFERENCES `Boards` (`board_id`)
);
CREATE TABLE `Comments` (
`comment_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`content` TEXT NOT NULL,
`user_id` BIGINT,
`post_id` BIGINT,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `Users` (`user_id`),
FOREIGN KEY (`post_id`) REFERENCES `Posts` (`post_id`)
);
CREATE TABLE `Post_Tags` (
`post_id` BIGINT,
`tag_id` BIGINT,
FOREIGN KEY (`post_id`) REFERENCES `Posts` (`post_id`),
FOREIGN KEY (`tag_id`) REFERENCES `Tags` (`tag_id`)
);
FactoryService
package com.jungmin.lib;
import com.jungmin.model.post.PostDao;
import com.jungmin.model.post.PostDaoImpl;
import com.jungmin.model.post.PostDto;
import com.jungmin.model.comment.CommentDao;
import com.jungmin.model.comment.CommentDaoImpl;
import com.jungmin.model.comment.CommentDto;
import com.jungmin.model.post_tag.Post_TagDao;
import com.jungmin.model.post_tag.PostTagDaoImpl;
import com.jungmin.model.post_tag.Post_TagDto;
import com.jungmin.model.board.BoardDao;
import com.jungmin.model.board.BoardDaoImpl;
import com.jungmin.model.board.BoardDto;
import com.jungmin.model.tag.TagDao;
import com.jungmin.model.tag.TagDaoImpl;
import com.jungmin.model.tag.TagDto;
import com.jungmin.model.users.UserDao;
import com.jungmin.model.users.UserDaoImpl;
import com.jungmin.model.users.UserDto;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;
public class FactoryService {
private final Mysql mysql = Mysql.getInstance();
private final UserDao userDao = new UserDaoImpl();
private final CommentDao commentDao = new CommentDaoImpl();
private final BoardDao boardDao = new BoardDaoImpl();
private final Post_TagDao postTagDao = new PostTagDaoImpl();
private final PostDao postDao = new PostDaoImpl();
private final TagDao tagDao = new TagDaoImpl();
private static FactoryService instance;
private FactoryService(){}
// Singleton
public static FactoryService getInstance() {
if (instance == null)
instance = new FactoryService();
return instance;
}
// initialize
public void init(Connection connection) throws SQLException {
mysql.query(connection, "DROP DATABASE IF EXISTS practiceSQL");
mysql.query(connection, "CREATE DATABASE practiceSQL");
mysql.query(connection, "USE practiceSQL");
}
// migration
// 작성된 schema.sql 파일을 통해 테이블 생성
public void migration(Connection connection) throws SQLException {
File file = new File("src/main/java/com/jungmin/migrations/schema.sql");
String absolutePath = file.getAbsolutePath(); //절대 경로 찾기
StringBuilder contentBuilder = new StringBuilder();
try (Stream<String> stream = Files.lines(Paths.get(absolutePath), StandardCharsets.UTF_8)) {
stream.forEach(contentBuilder::append);
} catch (IOException e) {
e.printStackTrace();
}
String[] querys = contentBuilder.toString().split(";");
Statement statement = connection.createStatement();
for(String str : querys) {
statement.execute(str);
}
statement.close();
}
// 검증 메서드
public ArrayList<HashMap<String,Object>> find(Connection connection, String table, String column) throws SQLException {
return mysql.selectQuery(connection, String.format("SELECT %s.%s FROM %s", table, column, table));
}
// part-2 테스트를 위한 컬럼값 셋팅
public void part2_setup() throws SQLException {
List<UserDto> users = new ArrayList<UserDto>() {{
add(new UserDto(1L, "luckykim", "luckykim@codestates.com", "password1", new Date(System.currentTimeMillis())));
add(new UserDto(2L, "lattekim", "lattekim@codestates.com", "password2", new Date(System.currentTimeMillis())));
add(new UserDto(3L, "nillava", "nillava@codestates.com", "password3", new Date(System.currentTimeMillis())));
add(new UserDto(4L, "jungminlee", "jungminlee@codestates.com", "password4", new Date(System.currentTimeMillis())));
add(new UserDto(5L, "johnsmith", "johnsmith@example.com", "password5", new Date(System.currentTimeMillis())));
add(new UserDto(6L, "janedoe", "janedoe@example.com", "password6", new Date(System.currentTimeMillis())));
add(new UserDto(7L, "alicewonder", "alicewonder@example.com", "password7", new Date(System.currentTimeMillis())));
add(new UserDto(8L, "bobbuilder", "bobbuilder@example.com", "password8", new Date(System.currentTimeMillis())));
add(new UserDto(9L, "charliebrown", "charliebrown@example.com", "password9", new Date(System.currentTimeMillis())));
add(new UserDto(10L, "davidclark", "davidclark@example.com", "password10", new Date(System.currentTimeMillis())));
}};
for(UserDto userDto : users) {
userDao.insert(userDto);
}
List<BoardDto> boards = new ArrayList<BoardDto>() {{
add(new BoardDto(1L, "General Discussion", "A place for general chat"));
add(new BoardDto(2L, "Tech Talk", "Discuss the latest in tech"));
add(new BoardDto(3L, "Announcements", "Official announcements and updates"));
add(new BoardDto(4L, "Support", "Get help and support here"));
add(new BoardDto(5L, "Off Topic", "Talk about anything and everything"));
add(new BoardDto(6L, "Gaming", "All things gaming"));
add(new BoardDto(7L, "Movies", "Discuss the latest movies and TV shows"));
add(new BoardDto(8L, "Music", "Talk about your favorite tunes"));
add(new BoardDto(9L, "Books", "Share your favorite reads"));
add(new BoardDto(10L, "Travel", "Share travel tips and experiences"));
}};
for(BoardDto boardDto : boards) {
boardDao.insert(boardDto);
}
List<PostDto> posts = new ArrayList<PostDto>() {{
add(new PostDto(1L, "Welcome to the forum!", "We're glad to have you here.", 1L, 1L, new Date(System.currentTimeMillis())));
add(new PostDto(2L, "New Tech Trends", "What's new in tech this year?", 2L, 2L, new Date(System.currentTimeMillis())));
add(new PostDto(3L, "Site Update", "We've made some updates to the site.", 3L, null, new Date(System.currentTimeMillis())));
add(new PostDto(4L, "Need help with my PC", "My PC won't start. Help!", 4L, 4L, new Date(System.currentTimeMillis())));
add(new PostDto(5L, "Random Chat", "Let's chat about anything!", 4L, 5L, new Date(System.currentTimeMillis())));
add(new PostDto(6L, "Favorite Games", "What are your favorite games?", 6L, null, new Date(System.currentTimeMillis())));
add(new PostDto(7L, "New Movie Releases", "What movies are you excited about?", 7L, 7L, new Date(System.currentTimeMillis())));
add(new PostDto(8L, "Music Recommendations", "Share your favorite songs.", 8L, null, new Date(System.currentTimeMillis())));
add(new PostDto(9L, "Book Club", "What books are you reading?", null, 9L, new Date(System.currentTimeMillis())));
add(new PostDto(10L, "Travel Tips", "Share your best travel tips.", 7L, 10L, new Date(System.currentTimeMillis())));
}};
for(PostDto postDto : posts) {
postDao.insert(postDto);
}
List<CommentDto> comments = new ArrayList<CommentDto>() {{
add(new CommentDto(1L, "Great post!", 2L, 1L, new Date(System.currentTimeMillis())));
add(new CommentDto(2L, "I totally agree.", 3L, 2L, new Date(System.currentTimeMillis())));
add(new CommentDto(3L, "Thanks for the update.", 4L, 3L, new Date(System.currentTimeMillis())));
add(new CommentDto(4L, "Have you tried restarting?", 5L, 4L, new Date(System.currentTimeMillis())));
add(new CommentDto(5L, "This is fun!", 6L, 5L, new Date(System.currentTimeMillis())));
add(new CommentDto(6L, "I love that game too.", 7L, 6L, new Date(System.currentTimeMillis())));
add(new CommentDto(7L, "Can't wait to see it.", 8L, 7L, new Date(System.currentTimeMillis())));
add(new CommentDto(8L, "Great recommendation.", 1L, 8L, new Date(System.currentTimeMillis())));
add(new CommentDto(9L, "I'm reading that too.", 1L, 9L, new Date(System.currentTimeMillis())));
add(new CommentDto(10L, "Thanks for the tips.", 1L, 10L, new Date(System.currentTimeMillis())));
}};
for(CommentDto commentDto : comments) {
commentDao.insert(commentDto);
}
List<TagDto> tags = new ArrayList<TagDto>() {{
add(new TagDto(1L, "Technology"));
add(new TagDto(2L, "Gaming"));
add(new TagDto(3L, "Movies"));
add(new TagDto(4L, "Music"));
add(new TagDto(5L, "Books"));
add(new TagDto(6L, "Travel"));
add(new TagDto(7L, "Food"));
add(new TagDto(8L, "Fitness"));
add(new TagDto(9L, "Education"));
add(new TagDto(10L, "Lifestyle"));
}};
for(TagDto tagDto : tags) {
tagDao.insert(tagDto);
}
List<Post_TagDto> postTags = new ArrayList<Post_TagDto>() {{
add(new Post_TagDto(1L, 1L));
add(new Post_TagDto(2L, 1L));
add(new Post_TagDto(3L, 2L));
add(new Post_TagDto(4L, 2L));
add(new Post_TagDto(5L, 3L));
add(new Post_TagDto(6L, 3L));
add(new Post_TagDto(7L, 4L));
add(new Post_TagDto(8L, 4L));
add(new Post_TagDto(9L, 5L));
add(new Post_TagDto(10L, 5L));
}};
for(Post_TagDto postTagDto: postTags) {
postTagDao.insert(postTagDto);
}
}
}
Mysql
package com.jungmin.lib;
import com.jungmin.script.Properties;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
public class Mysql {
// script/Properties.java 에서 사용자 정보 로드
private final String URL = Properties.getURL();
private final String USER_ID = Properties.getDatabaseUsername();
private final String USER_PWD = Properties.getDatabasePassword();
private static Mysql instance;
private Mysql() {
}
//Singleton
public static Mysql getInstance() {
if (instance == null)
instance = new Mysql();
return instance;
}
// 데이터베이스 접속
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER_ID, USER_PWD);
}
// 쿼리 메서드
public void query(Connection connection, String sql) throws SQLException {
Statement statement = connection.createStatement();
statement.execute(sql);
statement.close();
}
//ResultSet to Convert ArrayList<HashMap<>>
public ArrayList<HashMap<String,Object>> convertResultSetToArrayList(ResultSet rs) throws SQLException {
ResultSetMetaData md = rs.getMetaData();
int columns = md.getColumnCount();
ArrayList<HashMap<String,Object>> list = new ArrayList<HashMap<String,Object>>();
while(rs.next()) {
HashMap<String,Object> row = new HashMap<String, Object>(columns);
for(int i=1; i<=columns; ++i) {
if(!row.containsKey(md.getColumnName(i))) row.put(md.getColumnName(i), rs.getObject(i));
else row.put("roleName", rs.getObject(i));
}
list.add(row);
}
return list;
}
// 쿼리 메서드(Select 구문) ArrayList로 반환(위의 convertResultSetToArrayList 지정 메서드 사용)
public ArrayList<HashMap<String,Object>> selectQuery(Connection connection, String sql) throws SQLException {
Statement statement = connection.createStatement();
ArrayList<HashMap<String,Object>> response = convertResultSetToArrayList(statement.executeQuery(sql));
statement.close();
return response;
}
// 쿼리 메서드(Select 구문) 오버로딩
public ArrayList<HashMap<String,Object>> selectQuery(Connection connection, String table, String column) throws SQLException {
Statement statement = connection.createStatement();
ArrayList<HashMap<String,Object>> response = convertResultSetToArrayList(statement.executeQuery(String.format("SELECT %s.%s FROM %s", table, column, table)));
statement.close();
return response;
}
// 데이터베이스 접속 종료
public void terminate(Connection connection) throws SQLException {
if(connection != null && !connection.isClosed())
connection.close();
}
}
Properties
package com.jungmin.script;
public class Properties {
public static String URL = "jdbc:mysql://127.0.0.1:3306"; // 기본 설정값
public static String DATABASE_USERNAME = "root"; // 기본값은 root입니다.
public static String DATABASE_PASSWORD = "11"; // 본인의 패스워드로 변경해주세요.
public static String getURL() {
return URL;
}
public static String getDatabaseUsername() {
return DATABASE_USERNAME;
}
public static String getDatabasePassword() {
return DATABASE_PASSWORD;
}
}
Part1
package com.jungmin.script;
public class Part1 {
/*
----------------------------------------------------------------------------------------------
TODO: requirement를 참조하여, migration/schema.sql에 알맞은 테이블을 구성해주세요.
*/
/*
----------------------------------------------------------------------------------------------
TODO: Q 1-1. 현재 있는 데이터베이스에 존재하는 모든 테이블 정보를 보기위한 SQL을 작성해주세요.
*/
public static final String PART1_1 = "SHOW TABLES";
/*
----------------------------------------------------------------------------------------------
TODO: Q 1-2. Users 테이블의 구조를 보기위한 SQL을 작성해주세요.
- 요구사항에 맞는 Users 테이블을 작성해야만, 테스트를 통과합니다.
*/
public static final String PART1_2 = "DESC Users";
/*
----------------------------------------------------------------------------------------------
TODO: Q 1-3. Posts 테이블의 구조를 보기위한 SQL을 작성해주세요.
- 요구사항에 맞는 Posts 테이블을 작성해야만, 테스트를 통과합니다.
*/
public static final String PART1_3 = "DESC Posts";
/*
----------------------------------------------------------------------------------------------
TODO: Q 1-4. Tags 테이블의 구조를 보기위한 SQL을 작성해주세요.
- 요구사항에 맞는 Tags 테이블을 작성해야만, 테스트를 통과합니다.
*/
public static final String PART1_4 = "DESC Tags";
/*
----------------------------------------------------------------------------------------------
TODO: Q 1-5. Post_Tags 테이블의 구조를 보기위한 SQL을 작성해주세요.
- 요구사항에 맞는 Post_Tags 테이블을 작성해야만, 테스트를 통과합니다.
*/
public static final String PART1_5 = "DESC Post_tags";
/*
----------------------------------------------------------------------------------------------
TODO: Q 1-6. Comments 테이블의 구조를 보기위한 SQL을 작성해주세요.
- 요구사항에 맞는 Comments 테이블을 작성해야만, 테스트를 통과합니다.
*/
public static final String PART1_6 = "DESC Comments";
/*
----------------------------------------------------------------------------------------------
TODO: Q 1-7. Boards 테이블의 구조를 보기위한 SQL을 작성해주세요.
- 요구사항에 맞는 Boards 테이블을 작성해야만, 테스트를 통과합니다.
*/
public static final String PART1_7 = "DESC Boards";
}
Part1_Test
package com.jungmin;
import com.jungmin.lib.FactoryService;
import com.jungmin.lib.Mysql;
import com.jungmin.script.Properties;
import com.jungmin.script.Part1;
import org.junit.jupiter.api.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
public class Part1_Test {
private static Mysql mysql = Mysql.getInstance();
private static FactoryService factoryService = FactoryService.getInstance();
private static Connection connection = null;
// 테스트 시작전 initializr
@BeforeAll
public static void init() throws SQLException {
connection = mysql.getConnection();
factoryService.init(connection);
factoryService.migration(connection);
}
// 테스트 종료후 데이터베이스 자원 종료
@AfterAll
public static void terminate() throws SQLException {
mysql.terminate(connection);
}
@Test
@DisplayName("Q 1-0. 데이터베이스 접속 확인")
public void Connect_Test() throws SQLException {
System.out.printf("<YOUR DATABASE CONFIG>%n");
System.out.printf("URL : %s%n", com.jungmin.script.Properties.getURL());
System.out.printf("DATABASE_USERNAME : %s%n", Properties.getDatabaseUsername());
System.out.printf("DATABASE_PASSWORD : %s%n", com.jungmin.script.Properties.getDatabasePassword());
}
@Test
@DisplayName("Q 1-1. 현재 있는 데이터베이스에 존재하는 모든 테이블 정보를 보기위한 SQL을 작성해주세요.")
public void Query_Test_1_1() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part1.PART1_1);
System.out.println(response);
assertThat(response.size() >= 6).isTrue();
assertThat(response.toString().contains("Users")).isTrue();
assertThat(response.toString().contains("Tags")).isTrue();
assertThat(response.toString().contains("Posts")).isTrue();
assertThat(response.toString().contains("Post_Tags")).isTrue();
assertThat(response.toString().contains("Comments")).isTrue();
assertThat(response.toString().contains("Boards")).isTrue();
// 결과 출력
for(HashMap<String, Object> map: response) {
Set set = map.entrySet();
for (Object o : set) {
Map.Entry<String, Object> entry = (Map.Entry) o;
Object value = entry.getValue();
System.out.printf("TABLE : %s%n", value);
}
}
}
@Test
@DisplayName("Q 1-2. Users 테이블의 구조를 보기위한 SQL을 작성해주세요.")
public void Query_Test_1_2() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part1.PART1_2);
assertThat(response.size() >= 5).isTrue();
assertThat(response.get(0).toString().contains("user_id")).isTrue();
assertThat(response.get(0).toString().contains("PRI")).isTrue();
assertThat(response.get(0).toString().contains("bigint")).isTrue();
assertThat(response.get(1).toString().contains("username")).isTrue();
assertThat(response.get(1).toString().contains("varchar")).isTrue();
assertThat(response.get(2).toString().contains("email")).isTrue();
assertThat(response.get(2).toString().contains("varchar")).isTrue();
assertThat(response.get(3).toString().contains("password")).isTrue();
assertThat(response.get(3).toString().contains("varchar")).isTrue();
assertThat(response.get(4).toString().contains("created_at")).isTrue();
assertThat(response.get(4).toString().contains("datetime")).isTrue();
// 결과 출력
for(HashMap<String, Object> map: response) {
System.out.println(map);
}
}
@Test
@DisplayName("Q 1-3. Posts 테이블의 구조를 보기위한 SQL을 작성해주세요.")
public void Query_Test_1_3() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part1.PART1_3);
assertThat(response.size()).isEqualTo(6);
assertThat(response.get(0).toString().contains("post_id")).isTrue();
assertThat(response.get(0).toString().contains("PRI")).isTrue();
assertThat(response.get(0).toString().contains("bigint")).isTrue();
assertThat(response.get(1).toString().contains("title")).isTrue();
assertThat(response.get(1).toString().contains("varchar")).isTrue();
assertThat(response.get(2).toString().contains("content")).isTrue();
assertThat(response.get(5).toString().contains("created_at")).isTrue();
assertThat(response.get(5).toString().contains("CURRENT_TIMESTAMP")).isTrue();
assertThat(response.get(3).toString().contains("user_id")).isTrue();
assertThat(response.get(4).toString().contains("MUL")).isTrue();
// 결과 출력
for(HashMap<String, Object> map: response) {
System.out.println(map);
}
}
@Test
@DisplayName("Q 1-4. Tags 테이블의 구조를 보기위한 SQL을 작성해주세요.")
public void Query_Test_1_4() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part1.PART1_4);
assertThat(response.size()).isEqualTo(2);
assertThat(response.get(0).toString().contains("tag_id")).isTrue();
assertThat(response.get(0).toString().contains("PRI")).isTrue();
assertThat(response.get(0).toString().contains("bigint")).isTrue();
assertThat(response.get(1).toString().contains("name")).isTrue();
assertThat(response.get(1).toString().contains("varchar")).isTrue();
// 결과 출력
for(HashMap<String, Object> map: response) {
System.out.println(map);
}
}
@Test
@DisplayName("Q 1-5. Post_Tags 테이블의 구조를 보기위한 SQL을 작성해주세요.")
public void Query_Test_1_5() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part1.PART1_5);
assertThat(response.size()).isEqualTo(2);
assertThat(response.get(0).toString().contains("post_id")).isTrue();
assertThat(response.get(0).toString().contains("MUL")).isTrue();
assertThat(response.get(0).toString().contains("bigint")).isTrue();
assertThat(response.get(1).toString().contains("tag_id")).isTrue();
assertThat(response.get(1).toString().contains("MUL")).isTrue();
assertThat(response.get(1).toString().contains("bigint")).isTrue();
// 결과 출력
for(HashMap<String, Object> map: response) {
System.out.println(map);
}
}
@Test
@DisplayName("Q 1-6. Comments 테이블의 구조를 보기위한 SQL을 작성해주세요.")
public void Query_Test_1_6() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part1.PART1_6);
assertThat(response.size()).isEqualTo(5);
assertThat(response.get(0).toString().contains("comment_id")).isTrue();
assertThat(response.get(0).toString().contains("PRI")).isTrue();
assertThat(response.get(0).toString().contains("bigint")).isTrue();
assertThat(response.get(1).toString().contains("content")).isTrue();
assertThat(response.get(1).toString().contains("text")).isTrue();
assertThat(response.get(2).toString().contains("user_id")).isTrue();
assertThat(response.get(2).toString().contains("MUL")).isTrue();
assertThat(response.get(3).toString().contains("post_id")).isTrue();
assertThat(response.get(3).toString().contains("MUL")).isTrue();
assertThat(response.get(4).toString().contains("created_at")).isTrue();
assertThat(response.get(4).toString().contains("CURRENT_TIMESTAMP")).isTrue();
// 결과 출력
for(HashMap<String, Object> map: response) {
System.out.println(map);
}
}
@Test
@DisplayName("Q 1-7. Boards 테이블의 구조를 보기위한 SQL을 작성해주세요.")
public void Query_Test_1_7() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part1.PART1_7);
assertThat(response.size()).isEqualTo(3);
assertThat(response.get(0).toString().contains("board_id")).isTrue();
assertThat(response.get(0).toString().contains("PRI")).isTrue();
assertThat(response.get(0).toString().contains("bigint")).isTrue();
assertThat(response.get(1).toString().contains("name")).isTrue();
assertThat(response.get(1).toString().contains("varchar")).isTrue();
assertThat(response.get(2).toString().contains("description")).isTrue();
assertThat(response.get(2).toString().contains("text")).isTrue();
// 결과 출력
for(HashMap<String, Object> map: response) {
System.out.println(map);
}
}
}
Part2
package com.jungmin.script;
public class Part2 {
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-1. users 테이블에 존재하는 모든 컬럼을 포함한 모든 데이터를 확인하기 위한 SQL을 작성해주세요.
*/
public static final String PART2_1 = "SELECT * FROM Users";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-2. users 테이블에 존재하는 모든 데이터에서 username 컬럼만을 확인하기 위한 SQL을 작성해주세요.
*/
public static final String PART2_2 = "SELECT u.username FROM Users u";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-3. users 테이블에 데이터를 추가하기 위한 SQL을 작성해주세요.")
- 원하는 username, email, password를 사용하시면 됩니다.
*/
public static final String PART2_3 = "INSERT INTO Users(username, email, password) VALUES('이름', 'abc@naver.com', '123123')";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-4. users 테이블에서 특정 조건을 가진 데이터를 찾기위한 SQL을 작성해주세요.
- 조건 : username이 luckykim이여야 합니다.
*/
public static final String PART2_4 = "SELECT u.username FROM Users u WHERE u.username = 'luckykim'";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-5. users 테이블에서 특정 조건을 가진 데이터를 찾기위한 SQL을 작성해주세요.
- 조건 : username이 luckykim이 아니여야 합니다.
*/
public static final String PART2_5 = "SELECT u.username FROM Users u WHERE u.username <> 'luckykim'";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-6. posts 테이블에 존재하는 모든 데이터에서 title 컬럼만을 찾기 위한 SQL을 작성해주세요.
*/
public static final String PART2_6 = "SELECT p.title FROM Posts p";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-7. posts의 title과 그 게시글을 작성한 user의 username을 찾기 위한 SQL을 작성해주세요.
- 저자가 없더라도, posts의 title을 모두 찾아야합니다.
*/
public static final String PART2_7 = "SELECT p.title, u.username FROM Posts p LEFT JOIN Users u ON u.user_id = p.user_id";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-8. posts의 title과 그 게시글을 작성한 user의 username을 찾기 위한 SQL을 작성해주세요.
- 저자가 있는 posts의 title만 찾아야합니다.
*/
public static final String PART2_8 = "SELECT p.title, u.username FROM Posts p JOIN Users u ON u.user_id = p.user_id";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-9. posts 테이블의 데이터를 수정하기 위한 SQL을 작성해주세요.
- title이 'New Tech Trends'인 posts 데이터에서 content를 'Updated content'로 수정해야합니다.
*/
public static final String PART2_9 = "UPDATE Posts p SET p.title = 'Updated content' WHERE p.title = 'New Tech Trends'";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-10. posts 테이블의 데이터를 추가하기 위한 SQL을 작성해주세요.
- luckykim이 작성한 게시글을 추가해주세요. 제목과 본문은 자유입니다. (참고: luckykim의 아이디는 1입니다.)
*/
public static final String PART2_10 = "INSERT INTO Posts (title, content, user_id, board_id) " +
"VALUES ('title', 'content', 1, 10);";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-11. 어느 board에도 속하지 않는 post의 모든 데이터를 찾기위한 SQL을 작성해주세요.")
*/
public static final String PART2_11 = "SELECT * FROM Posts p WHERE p.board_id IS NULL";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-12. users 테이블의 user_id가 1인 유저가 작성한 post의 title을 찾기위한 SQL을 작성해주세요.
*/
public static final String PART2_12 = "SELECT p.title FROM Posts p WHERE p.user_id = 1";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-13. users 테이블의 user_id가 1인 유저가 작성한 post의 board name을 찾기위한 SQL을 작성해주세요.
*/
public static final String PART2_13 = "SELECT b.name FROM Boards b JOIN Posts p ON b.board_id = p.board_id WHERE p.user_id = 1";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-14. board의 name이 'General Discussion'인 post의 title, content, created_at을 찾기위한 SQL을 작성해주세요.
*/
public static final String PART2_14 = "SELECT p.title, p.content, p.created_at FROM Posts p JOIN Boards b ON b.board_id = p.board_id WHERE b.name = 'General Discussion'";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-15. luckykim이 작성한 comment의 개수 (컬럼명: CommentCount)를 출력하기 위한 SQL을 작성해주세요.
*/
public static final String PART2_15 = "SELECT COUNT(c.comment_id) as CommentCount FROM Comments c JOIN Users u ON c.user_id = u.user_id WHERE u.username = 'luckykim'";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-16. 각 user(컬럼명: username)가 작성한 comment의 개수 (컬럼명: CommentCount)를 출력하기 위한 SQL을 작성해주세요.
- 단, 0개의 comment를 작성한 유저도 모두 출력해야 합니다.
*/
public static final String PART2_16 = "SELECT u.username, COUNT(c.comment_id) as CommentCount FROM Comments c LEFT JOIN Users u ON c.user_id = u.user_id GROUP BY u.username";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-17. 각 board의 name과 해당 board에 속한 post의 개수 (컬럼명: PostCount)를 출력하기 위한 SQL을 작성해주세요.
*/
public static final String PART2_17 = "SELECT b.name, COUNT(p.post_id) as PostCount FROM Boards b JOIN Posts p ON b.board_id = p.board_id GROUP BY b.name";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-18. 각 board의 name과 해당 board에 속한 posts 테이블의 content의 평균 길이 (컬럼명: AvgLength)를 출력하기 위한 SQL을 작성해주세요.")
*/
public static final String PART2_18 = "SELECT b.name, AVG(LENGTH(p.content)) as AvgLength FROM Boards b LEFT JOIN Posts p ON b.board_id = p.board_id GROUP BY b.name";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-19. 각 board의 name과 해당 board에 속한 posts 테이블의 content의 평균 길이 (컬럼명: AvgLength)를 출력하기 위한 SQL을 작성해주세요.
- 단, content가 null인 경우를 제외해야 합니다.
*/
public static final String PART2_19 = "SELECT b.name, AVG(LENGTH(p.content)) as AvgLength FROM Boards b JOIN Posts p ON b.board_id = p.board_id GROUP BY b.name";
/*
----------------------------------------------------------------------------------------------
TODO: Q 2-20. 각 tag의 name과 해당 tag가 달린 post의 개수 (컬럼명: PostCount)를 출력하기 위한 SQL을 작성해주세요.
- 단, post에 할당되지 않은 tag의 이름도 모두 출력해야 합니다.")
*/
public static final String PART2_20 = "SELECT t.name, COUNT(pt.post_id) AS PostCount " +
"FROM Tags t " +
"LEFT JOIN Post_Tags pt ON t.tag_id = pt.tag_id " +
"GROUP BY t.name " +
"ORDER BY t.name";
}
Part2_Test
package com.jungmin;
import com.jungmin.lib.FactoryService;
import com.jungmin.lib.Mysql;
import com.jungmin.script.Part2;
import org.junit.jupiter.api.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
public class Part2_Test {
private static final Mysql mysql = Mysql.getInstance();
private static final FactoryService factoryService = FactoryService.getInstance();
private static Connection connection = null;
@BeforeEach
public void init() throws SQLException {
connection = mysql.getConnection();
factoryService.init(connection);
factoryService.migration(connection);
factoryService.part2_setup();
}
@AfterAll
public static void terminate() throws SQLException {
mysql.terminate(connection);
}
@Test
@DisplayName("Q 2-01. users 테이블에 존재하는 모든 컬럼을 포함한 모든 데이터를 확인하기 위한 SQL을 작성해주세요.")
public void Query_Test_2_1() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part2.PART2_1);
ArrayList<HashMap<String,Object>> factoryResponse = factoryService.find(connection, "users", "*");
assertThat(response.size()).isEqualTo(10);
assertThat(response).usingRecursiveComparison().isEqualTo(factoryResponse);
}
@Test
@DisplayName("Q 2-02. users 테이블에 존재하는 모든 데이터에서 username 컬럼만을 확인하기 위한 SQL을 작성해주세요.")
public void Query_Test_2_2() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part2.PART2_2);
ArrayList<HashMap<String,Object>> factoryResponse = factoryService.find(connection, "users", "username");
assertThat(response.size()).isEqualTo(10);
assertThat(response).usingRecursiveComparison().isEqualTo(factoryResponse);
}
@Test
@DisplayName("Q 2-03. users 테이블에 데이터를 추가하기 위한 SQL을 작성해주세요.")
public void Query_Test_2_3() throws SQLException {
mysql.query(connection, Part2.PART2_3);
ArrayList<HashMap<String,Object>> factoryResponse = factoryService.find(connection, "users", "*");
assertThat(factoryResponse.size()).isEqualTo(11);
}
@Test
@DisplayName("Q 2-04. users 테이블에서 특정 조건을 가진 데이터를 찾기위한 SQL을 작성해주세요.(username = 'luckykim'")
public void Query_Test_2_4() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part2.PART2_4);
System.out.println(response);
assertThat(response.size()).isEqualTo(1);
assertThat(response.get(0).get("username")).isEqualTo("luckykim");
}
@Test
@DisplayName("Q 2-05. users 테이블에서 특정 조건을 가진 데이터를 찾기위한 SQL을 작성해주세요. \n username이 'luckykim이 아니여야 합니다.")
public void Query_Test_2_5() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part2.PART2_5);
assertThat(response.size()).isEqualTo(9);
for (HashMap<String, Object> data : response) {
assertThat(data.get("username")).isNotEqualTo("luckykim");
}
}
@Test
@DisplayName("Q 2-06. posts 테이블에 존재하는 모든 데이터에서 title 컬럼만을 찾기 위한 SQL을 작성해주세요.")
public void Query_Test_2_6() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection, Part2.PART2_6);
ArrayList<HashMap<String,Object>> factoryResponse = factoryService.find(connection, "posts", "title");
assertThat(factoryResponse.size()).isEqualTo(10);
assertThat(response).usingRecursiveComparison().isEqualTo(factoryResponse);
}
@Test
@DisplayName("Q 2-07. posts의 title과 그 게시글을 작성한 user의 username을 찾기 위한 SQL을 작성해주세요. - 저자가 없더라도, 게시글의 title을 모두 찾아야합니다.")
public void Query_Test_2_7() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_7);
assertThat(response.size()).isEqualTo(10);
int count = 0;
for (HashMap<String, Object> data : response) {
if (data.get("username") == null) continue;
if (data.get("username").equals("luckykim")) count++;
}
assertThat(count).isEqualTo(1);
}
@Test
@DisplayName("Q 2-08. posts의 title과 그 게시글을 작성한 user의 username을 찾기 위한 SQL을 작성해주세요. - 저자가 있는 게시글의 title만 찾아야합니다.")
public void Query_Test_2_8() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_8);
assertThat(response.size()).isEqualTo(9);
}
@Test
@DisplayName("Q 2-09. posts 테이블의 데이터를 수정하기 위한 SQL을 작성해주세요. - title이 'New Tech Trends'인 posts 데이터에서 content를 'Updated content'로 수정해야합니다.")
public void Query_Test_2_9() throws SQLException {
mysql.query(connection, Part2.PART2_9);
ArrayList<HashMap<String,Object>> factoryResponse = factoryService.find(connection, "posts", "*");
for (HashMap<String, Object> data : factoryResponse) {
if (data.get("title").equals("New Tech Trends")) {
assertThat(data.get("content")).isEqualTo("Updated content");
}
}
}
@Test
@DisplayName("Q 2-10. posts 테이블의 데이터를 추가하기 위한 SQL을 작성해주세요. - luckykim이 작성한 게시글을 추가해주세요. 제목과 본문은 자유입니다. (참고: luckykim의 아이디는 1입니다.)")
public void Query_Test_2_10() throws SQLException {
mysql.query(connection, Part2.PART2_10);
ArrayList<HashMap<String,Object>> factoryResponse = factoryService.find(connection, "posts", "*");
assertThat(factoryResponse.size()).isEqualTo(11);
int count = 0;
for (HashMap<String, Object> data : factoryResponse) {
if (data.get("user_id") == null) continue;
long userId = (long)data.get("user_id");
if (userId == 1) count++;
}
assertThat(count).isEqualTo(2);
}
@Test
@DisplayName("Q 2-11. 어느 board에도 속하지 않는 post의 모든 데이터를 찾기위한 SQL을 작성해주세요.")
public void Query_Test_3_2_3() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_11);
assertThat(response.size()).isEqualTo(3);
}
@Test
@DisplayName("Q 2-12. users 테이블의 user_id가 1인 유저가 작성한 post의 title을 찾기위한 SQL을 작성해주세요.")
public void Query_Test_3_2_5() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_12);
assertThat(response.size()).isGreaterThan(0);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(1);
assertThat(map.get("title")).isEqualTo("Welcome to the forum!");
}
}
@Test
@DisplayName("Q 2-13. users 테이블의 user_id가 1인 유저가 작성한 post의 board name을 찾기위한 SQL을 작성해주세요.")
public void Query_Test_3_2_6() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_13);
assertThat(response.size()).isGreaterThan(0);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(1);
assertThat(map.get("name")).isEqualTo("General Discussion");
}
}
@Test
@DisplayName("Q 2-14. board의 name이 'General Discussion'인 post의 title, content, created_at을 찾기위한 SQL을 작성해주세요.")
public void Query_Test_3_2_7() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_14);
assertThat(response.size()).isGreaterThan(0);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(3);
assertThat(map.get("title")).isEqualTo("Welcome to the forum!");
assertThat(map.get("content")).isEqualTo("We're glad to have you here.");
}
}
@Test
@DisplayName("Q 2-15. luckykim이 작성한 comment의 개수 (컬럼명: CommentCount)를 출력하기 위한 SQL을 작성해주세요.")
public void Query_Test_3_2_9() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_15);
System.out.println(response);
assertThat(response.size()).isEqualTo(1);
assertThat(response.get(0).get("CommentCount")).isEqualTo(3L);
}
@Test
@DisplayName("Q 2-16. 각 user(컬럼명: username)가 작성한 comment의 개수 (컬럼명: CommentCount)를 출력하기 위한 SQL을 작성해주세요. \n 0개의 comment를 작성한 유저도 모두 출력해야 합니다.")
public void Query_Test_3_2_10() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_16);
assertThat(response.size()).isGreaterThan(0);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(2);
}
}
@Test
@DisplayName("Q 2-17. 각 board의 name과 해당 board에 속한 post의 개수 (컬럼명: PostCount)를 출력하기 위한 SQL을 작성해주세요.")
public void Query_Test_3_2_11() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_17);
assertThat(response.size()).isGreaterThan(0);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(2);
}
}
@Test
@DisplayName("Q 2-18. 각 board의 name과 해당 board에 속한 posts 테이블의 content의 평균 길이 (컬럼명: AvgLength)를 출력하기 위한 SQL을 작성해주세요.")
public void Query_Test_3_2_13() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_18);
assertThat(response.size()).isEqualTo(10);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(2);
}
}
@Test
@DisplayName("Q 2-19. 각 board의 name과 해당 board에 속한 posts 테이블의 content의 평균 길이 (컬럼명: AvgLength)를 출력하기 위한 SQL을 작성해주세요.\n 단, content가 null인 경우를 제외해야 합니다.")
public void Query_Test_3_2_14() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_19);
assertThat(response.size()).isEqualTo(7);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(2);
}
assertThat(response.get(6).get("name")).isEqualTo("Travel");
String avg = response.get(6).get("AvgLength").toString();
assertThat(avg).isEqualTo("28.0000");
}
@Test
@DisplayName("Q 2-20. 각 tag의 name과 해당 tag가 달린 post의 개수 (컬럼명: PostCount)를 출력하기 위한 SQL을 작성해주세요. \n 단, post에 할당되지 않은 tag의 이름도 모두 출력해야 합니다.")
public void Query_Test_3_2_16() throws SQLException {
ArrayList<HashMap<String,Object>> response = mysql.selectQuery(connection,
Part2.PART2_20);
assertThat(response.size()).isGreaterThan(0);
for(HashMap<String, Object> map: response) {
assertThat(map.size()).isEqualTo(2);
}
assertThat(response.get(0).get("name")).isEqualTo("Books");
assertThat(response.get(2).get("name")).isEqualTo("Fitness");
assertThat(response.get(3).get("PostCount")).isEqualTo(0L);
assertThat(response.get(7).get("name")).isEqualTo("Music");
assertThat(response.get(7).get("PostCount")).isEqualTo(2L);
}
}
Git 기초
다른 사람이 작성한 글이나 스스로 이전에 작성한 글을 보면, 맞춤법이 틀렸거나 잘못된 표현을 사용한 흔적을 발견할 수 있습니다. 개발자가 프로그래밍을 할 때에도, 문법이 틀렸거나 코드를 잘못 작성한 흔적을 발견할 수 있습니다. 이렇게 잘못 작성된 흔적을 발견하면, 정확한 표현으로 수정해야 합니다.
항상 올바른 표현으로 수정할 수 있다면 좋겠지만, 가끔 실수로 정확한 표현을 더 어색한 표현으로 변경하거나 파일의 내용을 삭제해 버리는 일이 발생합니다. 지금 실행 중인 텍스트 에디터에서는 Ctrl + z를 여러 번 눌러 이전 상태로 돌아갈 수 있습니다. 그러나 만약 코드를 수정한 뒤 에디터를 종료했다면, 다시 실행한 텍스트 에디터에서 이전 코드로 돌아갈 수 없습니다. 이런 경우를 위해서라도 이전에 작성한 내용을 보존해야 할 필요가 있습니다. 다행히 이전에 작성한 내용을 보존해 주는 시스템이 있습니다. 이 시스템이 바로, 버전 관리 시스템(Version Control System)입니다.
이번 유닛에서는 버전 관리 시스템 중 가장 많이 쓰이는 강력한 도구, Git을 학습합니다. Git을 학습하며 버전 관리와 협업의 기본을 학습합니다. 여러분이 실무에서 사용하는 협업 도구의 사용법을 익히고 나면, 여러분도 오픈 소스(Open Source : 소스 코드가 공개된 소프트웨어) 생태계에 기여할 수 있는 멋진 개발자가 될 수 있습니다.
학습목표
- 버전 관리 시스템의 필요성을 이해할 수 있다.
- Git의 핵심 기능을 이해할 수 있다.
- Git의 영역 및 핵심 개념을 이해할 수 있다.
- Git 명령어를 사용하여 Git의 협업, 백업 기능을 사용할 수 있다.
버전 관리 시스템 - Git
Git이란?
Git은 Linux OS를 만든 리누스 토르발즈가 만든 일종의 프로그램입니다. 리누스 토르발즈는 약 26년 동안 2천만 줄이 넘는 소스 코드를 1만 명이 넘는 소프트웨어 엔지니어들과 함께 오픈 소스 방식으로 작성하며 Linux OS를 만들고 관리했다고 합니다. 1만 명이 26년 동안 2천만 줄이 넘는 소스 코드를 함께 공유하며 작성하면서 얼마나 많은 불편함이 있었을까요?
이처럼 비효율적인 작업 환경을 해결하고자 리누스 토르발즈는 Git을 만들었습니다. 2005년에 Git을 출시하면서 그는 Git을 ‘지옥에서 온 문서 관리자’라고 소개했는데, Linux OS를 만들고 관리하면서 경험했던 지옥을 해결하기 위해 버전 관리, 백업, 그리고 협업과 관련된 기능들을 담아 Git을 탄생시켰습니다.
지금부터 Git이 무엇이고, Git으로 무엇을 할 수 있는지 알아봅시다.
Git은 쉽게 말해 파일을 관리해 주는 프로그램입니다. 여기에서 파일을 관리해 준다는 것은 다음을 의미합니다.
- 파일의 변경 사항을 추적하며, 사용자가 각 파일의 버전을 관리할 수 있게 도와줍니다.
- 파일을 백업할 수 있게 해 줍니다.
- 협업자들과 함께 파일을 공유하고, 각자의 작업물을 취합할 수 있게 해 줍니다.
Git이 무엇인지 알아보았으니, 위 내용을 토대로 조금 더 구체적으로 Git으로 무엇을 할 수 있는지 살펴봅시다.
Git으로 할 수 있는 것
버전 관리
초안, 수정1, 수정2, 수정3, 최종, 최종 수정, 진짜 최종, 진짜 최종 수정, 완성본, 완성본 수정, …
컴퓨터로 문서를 작성하면서 위와 같이 문서의 이름을 붙여본 경험이 보통 있으실 겁니다. 위에서 나열한 문서 이름들은 무엇을 의미하나요? 맞습니다. 버전을 의미합니다. 만약, 작성해야 하는 문서의 개수가 적고, 버전의 개수도 많지 않다면 위와 같은 방법으로 버전을 관리할 수도 있습니다.
그러나, 여러분이 작성해야 하는 문서가 100개 혹은 1000개이며, 각 문서별로 버전이 10~20개가 넘게 존재한다면 여러분들은 각 문서에 어떤 내용이 어떻게 수정되었는지 다 기억할 수 있나요? 기억하지 않고 수정 이력을 다른 곳에 메모해 둘 수도 있겠지만 비효율적이지 않을까요?
Git은 이러한 문제를 해결해 줍니다. Git이 관리하는 디렉토리(폴더)에 어떤 문서를 만들면 Git으로 그 문서의 버전을 관리할 수 있습니다. 이후 해당 문서를 수정할 때마다 언제 어떤 부분이 어떻게 수정되었는지에 대해 Git이 상세히 기록해 줍니다. 추후 이전의 버전으로 돌아가야 할 필요가 생기면 Git이 기록해 준 내용들을 확인하고 돌아가고자 하는 이전 버전을 선택해 손쉽게 버전을 되돌릴 수 있습니다.
또한, 특정 버전에서 두 버전으로 분기한 후, 분기한 두 버전을 각각 다르게 수정할 수도 있습니다. 예를 들어, 어떤 애플리케이션의 설치 설명서를 작성할 때, 기본적인 내용만 작성해 둔 버전에서 Windows용 버전, Mac용 버전으로 분기할 수 있습니다.
백업하기
여러분들이 실무에서 중요한 작업을 해두었는데, 컴퓨터가 고장 나 갑자기 작업물이 다 날아가버렸다면 어떨 것 같나요? 시간과 체력을 헛되이 날려버렸다는 생각에 상심이 매우 클 것입니다.
개발자들이 우스갯소리로 말하는 것들 중에, 백업의 중요성과 관련하여 확실한 것과 불확실한 것이 있습니다. 확실한 것은 여러분의 컴퓨터가 언젠가는 고장 난 다는 것이며, 불확실한 것은 여러분의 컴퓨터가 언제 고장 날지 모른다는 것입니다. 따라서 여러분은 앞으로 실무에서나 프로젝트를 진행하면서 백업을 자주 해야 합니다. Git은 여러분의 작업물을 온라인 원격 저장소에 백업할 수 있도록 백업 기능을 제공해 줍니다.
협업하기
Git은 여러 작업자가 하나의 작업물의 다른 부분을 각자 작업할 때, 작업물을 공유하고 취합할 수 있게 도와줍니다.
김코딩, 황코딩, 구코딩, 권코딩 4명이 함께 애국가를 문서로 작성하는 상황을 예로 들어보겠습니다. 이 4명은 각각 애국가를 한 절씩 맡았습니다. 이때, 아래의 프로세스로 협업을 진행할 수 있습니다.
4명이 각자 애국가를 한 절씩 작성합니다.
- 김코딩 : 1절
- 황코딩 : 2절
- 구코딩 : 3절
- 권코딩 : 4절
- 김코딩이 자신의 작업물을 원격 저장소에 업로드합니다.
- 황코딩이 김코딩의 작업물을 원격 저장소에서 다운로드합니다.
- 이때 Git이 김코딩이 작업한 1절과 황코딩이 작업한 2절을 취합해 줍니다.
- 취합이 잘 되었는지 확인한 황코딩은 1 ~ 2절 취합물을 원격 저장소에 업로드합니다.
- 구코딩이 1 ~ 2절 취합물을 원격 저장소에서 다운로드합니다.
- 이때, Git이 1 ~ 2절 취합물을 구코딩이 작업한 3절과 취합해 줍니다.
- 취합이 잘 되었는지 확인한 구코딩은 1 ~ 3절 취합물을 원격 저장소에 업로드합니다.
- 권코딩이 1 ~ 3절 취합물을 원격 저장소에서 다운로드합니다.
- 이때, Git이 1 ~ 3절 취합물을 권코딩이 작업한 4절과 취합해 줍니다.
- 취합이 잘 되었는지 확인한 권코딩은 1 ~ 4절 취합물을 원격 저장소에 업로드합니다.
- 이제 애국가 1 ~ 4절이 원격 저장소에 업로드되어 있으니 모든 팀원은 완성물을 공유할 수 있습니다.
만약, 김코딩과 황코딩이 같은 부분을 다르게 작업하면 어떻게 될까요? 이러한 경우, Git은 특정 부분의 수정 내용이 서로 다르다는 사실을 사용자에게 알려줍니다. 이때, 사용자는 어떤 내용을 반영할지 선택할 수 있습니다.
Git과 Github
일반적으로 Git 자체는 로컬에서 버전을 관리해 주는 프로그램을 의미합니다. 그러나, 여러분이 위에서 언급한 백업 기능 또는 협업을 위한 기능을 활용하려면 온라인 원격 저장소가 필요합니다. 이러한 원격 저장소 기능을 제공해 주는 서비스 중 하나가 Github입니다.
조금 더 구체적으로 설명하자면, Github은 Git이 설치되어 있는 클라우드 저장소입니다. 따라서, 로컬에서 Git이 관리하는 파일을 Github의 원격 저장소에 업로드하면, 업로드된 파일들에 대해 Git의 핵심 기능인 버전 관리 기능을 사용할 수 있으며, 온라인 저장소이므로 백업 및 다른 협업자들과 공유가 가능합니다.
한 문장으로 Git과 Github의 차이에 대해 정리해 보겠습니다.
Git은 로컬에서 버전을 관리해 주는 프로그램이며, Github은 Git을 클라우드 방식으로 구현한 서비스입니다.
위 그림에 지금까지 학습한 내용이 잘 요약되어 있습니다. 위 그림에 등장하는 용어들에 대해서는 이후의 콘텐츠에서 학습할 예정입니다. 여기에서는 아래와 같이 간략하게 이해해 주시고, Git이 무엇이고, 어떤 흐름으로 Git을 사용할 수 있는지에 초점을 맞추어 이해해 주시기 바랍니다.
- add, commit, push : 온라인 원격 저장소에 업로드하는 과정으로 이해해 주세요.
- fork, clone : 협업자의 작업물을 나의 로컬에 다운로드하는 과정으로 이해해 주세요.
- pull request : 상대 협업자에게 나의 작업 완성물을 취합해 달라고 요청하는 과정으로 이해해 주세요.
- merge : 상대방의 작업물과 나의 작업물을 취합하는 과정입니다.
지금까지 Git이 무엇인지, 그리고 어떤 기능을 제공하는지 살펴보았습니다. 이어지는 콘텐츠에서부터 Git을 설치하는 방법, 그리고 어떻게 Git의 유용한 기능들을 사용할 수 있는지에 대해 차근차근 배워봅시다.
Git 설치
이번 유닛에서는 로컬에 Git을 설치하고, 기본적인 환경 설정을 하는 방법을 학습합니다. 자신의 운영 체제에 맞는 콘텐츠를 골라 설치를 차근차근 진행해 주시면 됩니다.
학습목표
- Git을 설치하고, 환경 설정을 할 수 있다.
- SSH 키를 생성하여 Github에 등록할 수 있다.
Git 환경설정
Git을 이용할 때 필요한 환경 설정은 사용자 정보와 에디터 설정입니다.
사용자 정보
Git을 설치하면 가장 먼저, 사용자 이름과 이메일 주소를 설정합니다. 설정에 기록된 사용자 이름과 메일 주소를 앞으로 진행할 Git 커밋 내역에 기록합니다.
터미널 화면에 다음과 같이 입력하세요. (나의 사용자 이름과 내 이메일 주소를 대신해 Github에 등록된 사용자 이름과 이메일 주소를 사용하세요.)
$ git config --global user.name "나의 사용자 이름"
$ git config --global user.email "내 이메일 주소"
예를 들어 김코딩의 경우라면 다음과 같이 입력할 수 있을 것입니다.
$ git config --global user.name "kimcoding"
$ git config --global user.email "kimcoding@example.com"
- global 옵션으로 설정하면, 사용자 홈에 저장되므로 git을 설정할 때 처음에 단 한 번만 입력해도 됩니다. 나중에 github의 사용자 이름이나 이메일을 변경한다면, 이 명령어를 다시 입력해야 합니다.
- 만약 여러 프로젝트를 진행하고 있어서, 프로젝트마다 다른 사용자 이름과 이메일 주소를 사용하고 싶으면 global 옵션을 빼고 명령을 실행할 수 있습니다.
에디터
Git에서 커밋 메시지를 기록할 때, 특히 merge commit 확인 메시지가 나올 때 텍스트 에디터가 열립니다. 기본값으로 텍스트 에디터 vi가 열리는데, vi에 익숙하지 않다면, nano로 변경하는 편이 좋습니다.
$ git config --global core.editor nano
(Optional) CLI 사용을 위한 인증 과정
Github CLI는 브라우저에서 GUI로 사용하는 Github의 기능을 터미널에서 CLI로 사용할 수 있게 해주는 프로그램입니다. Github CLI를 사용하기 위해서는 인증 과정이 필요합니다. 수강생 여러분들이 사용할 수 있는 가장 쉽고 빠른 방법인 OAuth (Device Authorization) 인증 과정을 진행하겠습니다.
1. 먼저 GitHub CLI를 설치합니다.
- [Mac OS] macOS에서는 homebrew 설치 후, 다음 명령을 이용하여 CLI를 설치합니다.
$ brew install gh
- [Windows] git bash에서 gh --version을 입력하여 설치가 잘 되었는지 확인합니다.
- 참고로, git bash를 연 상태에서 Github CLI를 설치하셨다면, git bash를 종료했다가 다시 실행시켜주셔야 합니다.
$ gh --version
2. winpty gh auth login 명령어를 통해 로그인을 시도합니다. 화살표 키를 이용해 다음 항목들을 선택 후 Enter를 누릅니다.
? What accout do you want to log into? GitHub.com ? What is your preferred protocol for Git Operations? HTTPS ? Authenticate Git with your GitHub credentials? Yes ? How would you like to authenticate GitHub CLI? Login with a web browser
과정 중 실수가 발생했다면, Ctrl + C를 눌러 중단할 수 있습니다.
3. Login with a web browser 옵션을 선택하면, 다음과 같이 one-time code 가 등장합니다. 이 코드를 잘 메모해 두고 Enter키를 누르세요. Enter 키를 누르면 Device Activation을 가능하게 하는 창이 등장합니다.
4. 모든 인증 과정이 성공적으로 끝나면, 터미널 화면에서도 Logged in as 사용자이름과 같이 인증이 완료됩니다.