Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2단계 - QueryBuilder DDL #225

Open
wants to merge 6 commits into
base: parkseoldev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/main/java/domain/Person.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

@Table(name = "users")
@Entity
public class Person {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "nick_name")
private String name;

@Column(name = "old")
private Integer age;

@Column(nullable = false)
private String email;

@Transient
private Integer index;
}
13 changes: 13 additions & 0 deletions src/main/java/persistence/Application.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package persistence;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import database.DatabaseServer;
import database.H2;
import domain.Person;
import jdbc.JdbcTemplate;
import persistence.sql.ddl.CreateQueryBuilder;
import persistence.sql.ddl.DropQueryBuilder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -17,6 +27,9 @@ public static void main(String[] args) {

final JdbcTemplate jdbcTemplate = new JdbcTemplate(server.getConnection());

jdbcTemplate.execute(new CreateQueryBuilder().getCreateTableSql());
jdbcTemplate.execute(new DropQueryBuilder().getDropTableSql());

server.stop();
} catch (Exception e) {
logger.error("Error occurred", e);
Expand Down
135 changes: 135 additions & 0 deletions src/main/java/persistence/sql/ddl/CreateQueryBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package persistence.sql.ddl;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.lang.reflect.Field;

import domain.Person;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

public class CreateQueryBuilder {
Map<? extends Class<? extends Serializable>, String> javaClassTypeToDbTypes = Map.of(
Long.class, "BIGINT",
String.class, "VARCHAR",
Integer.class, "BIGINT"
);

public String getCreateTableSql() throws
NoSuchMethodException,
InvocationTargetException,
InstantiationException,
IllegalAccessException {

Class<Person> personClass = Person.class;
Comment on lines +23 to +29

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 해당 쿼리를 만드는 메서드의 기능이 Person 클래스에 한정되어있는데요!

파라미터로 클래스를 받아서 다른 클래스에 대해서도 기능이 동작하도록 개선해보는건 어떨까요!?

public String getCreateTableSql(Class<?> clazz)  {
    // 내부 구현..
}


Person person = personClass.getConstructor().newInstance();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용되지 않는 변수를 제거해주시면 좋을 것 같아요! 🙏


// 목표 출력의 목표를 스트링으로 적는다
/*
CREATE TABLE person (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
age INT,
email VARCHAR(50) NOT NULL,
index INT
);
*/
// 메서드 분리 (3)
Field idField = Arrays.stream(getDeclaredFields(personClass))
.filter(x -> x.isAnnotationPresent(GeneratedValue.class))
.findFirst().get();

String idFieldName = idField.getName();

String idType = javaClassTypeToDbTypes.get(idField.getType());

String generatedValue = generateValue();

String primaryKey = getPrimaryKey();

String idCombi = String.format("%s %s %s %s,", idFieldName, idType, generatedValue, primaryKey);

// name VARCHAR(50),
Field nameField = Arrays.stream(getDeclaredFields(personClass))
.filter(x -> x.isAnnotationPresent(Column.class)).findFirst().get();

String name = nameField.getName();

String nameType = javaClassTypeToDbTypes.get(nameField.getType());

String nameCombi = String.format("%s %s,", name, nameType);

// age INT,
Field ageField = Arrays.stream(getDeclaredFields(personClass))
.filter(x -> x.isAnnotationPresent(Column.class))
.filter(x -> x.getAnnotation(Column.class).name().equals("old"))
.findFirst().get();
String age = ageField.getName();
String ageType = javaClassTypeToDbTypes.get(ageField.getType());

String ageCombi = String.format("%s %s,", age, ageType);

// email VARCHAR(50) NOT NULL,
Field emailField = Arrays.stream(getDeclaredFields(personClass))
.filter(x -> x.isAnnotationPresent(Column.class))
.filter(x -> !x.getAnnotation(Column.class).nullable())
.findFirst().get();
String email = emailField.getName();
String emailType = javaClassTypeToDbTypes.get(emailField.getType());
boolean isNullable = emailField.getAnnotation(Column.class).nullable();

String emialNullable = isNull(isNullable);

String emailCombi = String.format("%s %s %s", email, emailType, emialNullable);
Comment on lines +78 to +89

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현해주신 여러 필드로 쿼리를 만들때 모두 아래와 같은 동일한 과정을 거치고 있는데요!
각각의 필드를 별도로 수행하지 않고 함께 추상화해서 기능을 수행할 수 있도록 개선해보면 좋을 것 같아요! 🙏

  1. 클래스의 모든 필드를 조회한다.
  2. 조회한 필드 중 @Column 이 선언된 필드를 조회한다.
  3. @Transient 를 포함하는 필드를 제외한다
  4. 필드의 이름, 타입, 옵션(nullalbe 여부 등) 정보를 조회한다.
  5. 쿼리를 만든다.


// index INT
Field indexField = Arrays.stream(getDeclaredFields(personClass))
.filter(x -> x.isAnnotationPresent(Transient.class))
.findFirst().get();
String indexCombi = "";

String tableName = personClass.getSimpleName();

// List<? extends Class<?>> fieldTypes = Arrays.stream(getDeclaredFields(personClass)).map(Field::getType).toList();
//
// List<String> fieldNames = Arrays.stream(getDeclaredFields(personClass)).map(Field::getName).toList();

String createTableSql = String.format("create table %s(%s %s %s %s %s)",
tableName,
idCombi,
nameCombi,
ageCombi,
emailCombi,
indexCombi
);

return createTableSql;
}

private String getPrimaryKey() {

return "PRIMARY KEY";
}

private static Field[] getDeclaredFields(Class<Person> personClass) {
return personClass.getDeclaredFields();
}

// DB에서 가져와야겠다 ai를 //
private static String generateValue() {
return "AUTO_INCREMENT";
}
Comment on lines +124 to +127

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터베이스 마다 해당 기능에 대한 쿼리가 다를 것이라고 생각하고 고민해주신 것 같아요! 👍 👍

다만 지금은 하나의 DB 라고 생각하고 구현 범위를 좁혀서 개발해보시는 것을 추천드립니다!

그렇다면 해당 문자열은 아래 처럼 상수로 만들어 사용할 수도 있겠네요!

private static final AUTO_INCREMENT_QUERY = "AUTO_INCREMENT"

getPrimaryKey()도 동일한 피드백입니다! 😄


private static String isNull(boolean isNullable) {
if (!isNullable) {
return "NOT NULL";
}
return "";
}
Comment on lines +129 to +134

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static을 제거하고 사용해도 좋을 것 같아요! getDeclaredFields() 도 동일한 피드백입니다!

Suggested change
private static String isNull(boolean isNullable) {
if (!isNullable) {
return "NOT NULL";
}
return "";
}
private String isNull(boolean isNullable) {
if (!isNullable) {
return "NOT NULL";
}
return "";
}

}
17 changes: 17 additions & 0 deletions src/main/java/persistence/sql/ddl/DropQueryBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package persistence.sql.ddl;

import domain.Person;

public class DropQueryBuilder {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DropQueryBuilder 에 대한 테스트도 함께 작성되어도 좋을 것 같아요 😄

public String getDropTableSql() {
Class<Person> personClass = Person.class;

String tableName = personClass.getSimpleName();
// DROP TABLE Person
String dropTableSql = String.format("drop table %s", tableName);

return dropTableSql;
}


}
21 changes: 21 additions & 0 deletions src/test/java/persistence/sql/ddl/CreateQueryBuilderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package persistence.sql.ddl;

import static org.junit.jupiter.api.Assertions.*;

import java.lang.reflect.InvocationTargetException;

import org.junit.jupiter.api.Test;

class CreateQueryBuilderTest {
@Test
void createDDL() throws
InvocationTargetException,
NoSuchMethodException,
InstantiationException,
IllegalAccessException {
CreateQueryBuilder createQueryBuilder = new CreateQueryBuilder();
String createTableSql = createQueryBuilder.getCreateTableSql();

assertEquals(createTableSql, "CREATE TABLE person (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, age INT)");
}
Comment on lines +10 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소하지만 테스트 시에는 아래와 같이 예외의 상위 클래스인 Exception을 사용하면 잘 정리할 수 있습니다!

    @Test
    void createDDL() throws Exception {

그리고 Person 클래스를 선언할 때 아래와 같이 데이터베이스에 사용되는 테이블 명이나 컬럼 명을 설정 해주었었는데요!
아래와 같이 쿼리 문에 함께 반영되면 좋을 것 같습니다! 😄

CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    nick_name VARCHAR(255),
    old INTEGER,
    email VARCHAR(255) NOT NULL
);

}
32 changes: 20 additions & 12 deletions src/test/java/persistence/study/ReflectionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

public class ReflectionTest {
private static final Logger logger = LoggerFactory.getLogger(ReflectionTest.class);
Expand All @@ -23,21 +23,21 @@ void showCarClass() {
logger.debug(carClass.getName());

Field[] fields = carClass.getDeclaredFields();
System.out.println("필드:");
logger.debug("필드:");
for (Field field : fields) {
System.out.println(field);
logger.debug(String.valueOf(field));
}

Constructor<?>[] constructors = carClass.getDeclaredConstructors();
System.out.println("생성자:");
logger.debug("생성자:");
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
logger.debug(String.valueOf(constructor));
}

Method[] methods = carClass.getDeclaredMethods();
System.out.println("메서드");
logger.debug("메서드:");
for (Method method : methods) {
System.out.println(method);
logger.debug(String.valueOf(method));
}
}

Expand All @@ -46,7 +46,7 @@ void showCarClass() {
Class<Car> carClass = Car.class;
Car car = carClass.getConstructor().newInstance();
Arrays.stream(carClass.getDeclaredMethods())
.filter(x -> x.getName().contains("test"))
.filter(x -> x.getName().startsWith("test"))
.forEach(x -> {
try {
x.invoke(car);
Expand Down Expand Up @@ -88,8 +88,12 @@ void showCarClass() {
priceField.setAccessible(true);
priceField.set(car, price);

assertThat(car.testGetName()).isEqualTo("test : " + name);
assertThat(car.testGetPrice()).isEqualTo("test : " + price);
assertAll(
() -> {
assertEquals(car.testGetName(), "test : " + name);
assertEquals(car.testGetPrice(), "test : " + price);
}
);
}

@Test
Expand All @@ -103,7 +107,11 @@ void showCarClass() {
.findFirst().orElseThrow();
Car car = (Car) constructor.newInstance(name, price);

assertThat(car.testGetName()).isEqualTo("test : " + name);
assertThat(car.testGetPrice()).isEqualTo("test : " + price);
assertAll(
() -> {
assertEquals(car.testGetName(), "test : " + name);
assertEquals(car.testGetPrice(), "test : " + price);
}
);
}
}