diff --git a/src/main/java/persistence/sql/common/Person.java b/src/main/java/persistence/sql/common/Person.java new file mode 100644 index 000000000..47f00a03f --- /dev/null +++ b/src/main/java/persistence/sql/common/Person.java @@ -0,0 +1,47 @@ +package persistence.sql.common; + +import jakarta.persistence.*; + +@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; + + public Person() { + + } + + public Person(Long id) { + this.id = id; + } + + public Person(Long id, String name, Integer age, String email, Integer index) { + this.id = id; + this.name = name; + this.age = age; + this.email = email; + this.index = index; + } + + public Person(String name, Integer age, String email, Integer index) { + this.name = name; + this.age = age; + this.email = email; + this.index = index; + } + +} diff --git a/src/main/java/persistence/sql/ddl/DDLHelper.java b/src/main/java/persistence/sql/ddl/DDLHelper.java index 8f893a41e..f25a9afe3 100644 --- a/src/main/java/persistence/sql/ddl/DDLHelper.java +++ b/src/main/java/persistence/sql/ddl/DDLHelper.java @@ -2,9 +2,10 @@ import jakarta.persistence.Entity; import jakarta.persistence.Table; -import persistence.sql.ddl.model.Column; +import persistence.sql.model.ColumnFactory; + import java.util.Arrays; -import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; @@ -14,9 +15,9 @@ public String getCreateQuery(Class clazz) { var tableName = getTableName(clazz); String columnInfo = Arrays.stream(clazz.getDeclaredFields()) - .map(Column::from) - .filter(Objects::nonNull) - .map(Column::getDDLColumnQuery) + .map(ColumnFactory::createColumn) + .filter(Optional::isPresent) + .map(column -> column.get().getDDLColumnQuery()) .collect(Collectors.joining(", ")); return "create table " + tableName + "(" + columnInfo + ");"; diff --git a/src/main/java/persistence/sql/ddl/Person.java b/src/main/java/persistence/sql/ddl/Person.java deleted file mode 100644 index 7326d3d54..000000000 --- a/src/main/java/persistence/sql/ddl/Person.java +++ /dev/null @@ -1,23 +0,0 @@ -package persistence.sql.ddl; - -import jakarta.persistence.*; - -@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; -} diff --git a/src/main/java/persistence/sql/dml/DMLHelper.java b/src/main/java/persistence/sql/dml/DMLHelper.java new file mode 100644 index 000000000..48fe84c9b --- /dev/null +++ b/src/main/java/persistence/sql/dml/DMLHelper.java @@ -0,0 +1,132 @@ +package persistence.sql.dml; + +import jakarta.persistence.Column; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import persistence.sql.model.ColumnFactory; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + + +public class DMLHelper { + private static final String INSERT_QUERY_FORMAT = "insert into %s (%s) values (%s);"; + private static final String FIND_ALL_QUERY_FORMAT = "select * from %s;"; + private static final String FIND_QUERY_FORMAT = "select * from %s %s;"; + private static final String DELETE_QUERY_FORMAT = "delete from %s %s;"; + + public String getInsertQuery(Class clazz, Object object) { + return String.format(INSERT_QUERY_FORMAT, getTableName(clazz), columnsClause(clazz), valueClause(object)); + } + + public String getFindAllQuery(Class clazz) { + return String.format(FIND_ALL_QUERY_FORMAT, getTableName(clazz)); + } + + public String getFindByIdQuery(Class clazz, Object id) { + try { + var idField = Arrays.stream(clazz.getDeclaredFields()) + .filter(field -> { + var column = ColumnFactory.createColumn(field); + return column.isPresent() && column.get().isPK(); + }) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("기본키가 존재하지 않습니다.")); + + var object = clazz.getDeclaredConstructor().newInstance(); + idField.setAccessible(true); + idField.set(object, id); + + return String.format(FIND_QUERY_FORMAT, getTableName(clazz), whereClause(object)); + } catch (Exception e) { + throw new IllegalArgumentException("생성 불가 한 class 입니다."); + } + } + + public String getDeleteQuery(Class clazz, Object object) { + return String.format( + DELETE_QUERY_FORMAT, + getTableName(clazz), + whereClause(object) + ); + } + + private String whereClause(Object object) { + var clazz = object.getClass(); + + return "where " + Arrays.stream(clazz.getDeclaredFields()) + .map(field -> { + var columnValue = getColumnValue(field, object); + var column = ColumnFactory.createColumn(field); + if (columnValue.isEmpty() || column.isEmpty()) return null; + return String.format("%s = %s", column.get().getName(), columnValue.get()); + } + ) + .filter(Objects::nonNull) + .collect(Collectors.joining(" and ")); + } + + private String getTableName(Class clazz) { + var annotations = clazz.getAnnotations(); + return Arrays.stream(annotations) + .filter(annotation -> annotation.annotationType().equals(Table.class)) + .map(table -> ((Table) table).name()) + .findFirst() + .orElse(clazz.getSimpleName()); + } + + private String columnsClause(Class clazz) { + var fields = clazz.getDeclaredFields(); + return Arrays.stream(fields) + .filter(this::isInsertQueryTarget) + .map(ColumnFactory::createColumn) + .filter(Optional::isPresent) + .map(column -> column.get().getName()) + .collect(Collectors.joining(", ")); + } + + private String valueClause(Object object) { + var clazz = object.getClass(); + return Arrays.stream(clazz.getDeclaredFields()) + .filter(this::isInsertQueryTarget) + .map(field -> getColumnValue(field, object)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.joining(", ")); + } + private boolean isInsertQueryTarget(Field field) { + var isId = false; + var isGeneratedValue = false; + var isColumn = false; + + for (Annotation annotation : field.getAnnotations()) { + if (annotation instanceof Column) { + isColumn = true; + } else if (annotation instanceof Id) { + isId = true; + } else if (annotation instanceof GeneratedValue) { + isGeneratedValue = true; + } + } + + return isColumn || (isId && !isGeneratedValue); + } + + private Optional getColumnValue(Field field, Object object) { + try { + field.setAccessible(true); + var column = ColumnFactory.createColumn(field); + if (column.isPresent()) { + return Optional.ofNullable(column.get().getQueryValue(field.get(object))); + } + return Optional.empty(); + } catch (IllegalAccessException e) { + throw new IllegalStateException("접근할 수 없는 필드입니다."); + } + } +} diff --git a/src/main/java/persistence/sql/ddl/model/Column.java b/src/main/java/persistence/sql/model/Column.java similarity index 57% rename from src/main/java/persistence/sql/ddl/model/Column.java rename to src/main/java/persistence/sql/model/Column.java index 2e3c65555..3d54f23e6 100644 --- a/src/main/java/persistence/sql/ddl/model/Column.java +++ b/src/main/java/persistence/sql/model/Column.java @@ -1,9 +1,9 @@ -package persistence.sql.ddl.model; - -import jakarta.persistence.Id; +package persistence.sql.model; import java.lang.reflect.Field; import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; public class Column { private String name; @@ -22,30 +22,16 @@ public Condition getCondition() { return condition; } - public Column(String name, Type type, Condition condition) { + Column(String name, Type type, Condition condition) { this.name = name; this.type = type; this.condition = condition; } - public static Column from(Field field) { - var isPKColumn = Arrays.stream(field.getAnnotations()) - .anyMatch(annotation -> annotation.annotationType().equals(Id.class)); - - if (isPKColumn) return PKColumn.from(field); - var columnAnnotation = Arrays.stream(field.getAnnotations()) - .filter(annotation -> annotation.annotationType().equals(jakarta.persistence.Column.class)) - .map(annotation -> (jakarta.persistence.Column) annotation) - .findFirst(); - - if (columnAnnotation.isEmpty()) return null; - var column = columnAnnotation.get(); - - var name = column.name().isBlank() ? field.getName() : column.name(); - var type = Type.from(field.getType()); - var condition = Condition.from(column); - - return new Column(name, type, condition); + Column(String name, Type type) { + this.name = name; + this.type = type; + this.condition = Condition.DEFAULT_CONDITION; } public String getDDLColumnQuery() { @@ -57,4 +43,23 @@ public String getDDLColumnQuery() { if (condition.isUnique()) sb.append(" ").append("UNIQUE"); return sb.toString(); } + + public String getQueryValue(Object value) { + if (Objects.isNull(value)) return null; + if (type == Type.VARCHAR) { + return String.format("'%s'", value); + } + return String.valueOf(value); + } + + public boolean isPK() { + return false; + } + + protected static Optional getColumnAnnotation(Field field) { + return Arrays.stream(field.getAnnotations()) + .filter(annotation -> annotation.annotationType().equals(jakarta.persistence.Column.class)) + .map(annotation -> (jakarta.persistence.Column) annotation) + .findFirst(); + } } diff --git a/src/main/java/persistence/sql/model/ColumnFactory.java b/src/main/java/persistence/sql/model/ColumnFactory.java new file mode 100644 index 000000000..8305a33a9 --- /dev/null +++ b/src/main/java/persistence/sql/model/ColumnFactory.java @@ -0,0 +1,23 @@ +package persistence.sql.model; + +import jakarta.persistence.Id; +import jakarta.persistence.Transient; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Optional; + +public class ColumnFactory { + public static Optional createColumn(Field field) { + boolean isPKColumn = false; + var isTransient = false; + for(var annotation : field.getAnnotations()) { + if (annotation.annotationType().equals(Id.class)) isPKColumn = true; + if (annotation.annotationType().equals(Transient.class)) isTransient = true; + } + + if (isTransient) return Optional.empty(); + if (isPKColumn) return Optional.of(PKColumn.from(field)); + return Optional.of(SimpleColumn.from(field)); + } +} diff --git a/src/main/java/persistence/sql/ddl/model/Condition.java b/src/main/java/persistence/sql/model/Condition.java similarity index 94% rename from src/main/java/persistence/sql/ddl/model/Condition.java rename to src/main/java/persistence/sql/model/Condition.java index d9d0f6ee0..0912d131d 100644 --- a/src/main/java/persistence/sql/ddl/model/Condition.java +++ b/src/main/java/persistence/sql/model/Condition.java @@ -1,4 +1,4 @@ -package persistence.sql.ddl.model; +package persistence.sql.model; public class Condition { public static final Condition DEFAULT_CONDITION = new Condition(false, true); diff --git a/src/main/java/persistence/sql/ddl/model/PKColumn.java b/src/main/java/persistence/sql/model/PKColumn.java similarity index 83% rename from src/main/java/persistence/sql/ddl/model/PKColumn.java rename to src/main/java/persistence/sql/model/PKColumn.java index 2538ec876..289d9c6aa 100644 --- a/src/main/java/persistence/sql/ddl/model/PKColumn.java +++ b/src/main/java/persistence/sql/model/PKColumn.java @@ -1,11 +1,10 @@ -package persistence.sql.ddl.model; +package persistence.sql.model; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import java.lang.reflect.Field; import java.util.Arrays; -import java.util.Objects; public class PKColumn extends Column { private String strategy; @@ -16,10 +15,7 @@ public PKColumn(String name, Type type, Condition condition, String strategy) { } public static PKColumn from(Field field) { - var column = Arrays.stream(field.getAnnotations()) - .filter(annotation -> annotation.annotationType().equals(jakarta.persistence.Column.class)) - .map(annotation -> (jakarta.persistence.Column) annotation) - .findFirst(); + var column = getColumnAnnotation(field); var name = column.filter(value -> !value.name().isBlank()) .map(jakarta.persistence.Column::name) @@ -56,4 +52,9 @@ public String getDDLColumnQuery() { sb.append(" PRIMARY KEY"); return sb.toString(); } + + @Override + public boolean isPK() { + return true; + } } diff --git a/src/main/java/persistence/sql/model/SimpleColumn.java b/src/main/java/persistence/sql/model/SimpleColumn.java new file mode 100644 index 000000000..0b43e06e5 --- /dev/null +++ b/src/main/java/persistence/sql/model/SimpleColumn.java @@ -0,0 +1,28 @@ +package persistence.sql.model; + +import java.lang.reflect.Field; +import java.util.Arrays; + +public class SimpleColumn extends Column { + public SimpleColumn(String name, Type type, Condition condition) { + super(name, type, condition); + } + + public SimpleColumn(String name, Type type) { + super(name, type); + } + + public static SimpleColumn from(Field field) { + var columnAnnotation = getColumnAnnotation(field); + if (columnAnnotation.isEmpty()) { + return new SimpleColumn(field.getName(), Type.from(field.getType())); + } + + var column = columnAnnotation.get(); + var name = column.name().isBlank() ? field.getName() : column.name(); + var type = Type.from(field.getType()); + var condition = Condition.from(column); + + return new SimpleColumn(name, type, condition); + } +} diff --git a/src/main/java/persistence/sql/ddl/model/Type.java b/src/main/java/persistence/sql/model/Type.java similarity index 94% rename from src/main/java/persistence/sql/ddl/model/Type.java rename to src/main/java/persistence/sql/model/Type.java index 47c00d994..79cc714de 100644 --- a/src/main/java/persistence/sql/ddl/model/Type.java +++ b/src/main/java/persistence/sql/model/Type.java @@ -1,4 +1,4 @@ -package persistence.sql.ddl.model; +package persistence.sql.model; import java.sql.Types; import java.util.Arrays; diff --git a/src/test/java/persistence/sql/ddl/DDLHelperTest.java b/src/test/java/persistence/sql/ddl/DDLHelperTest.java index 0650ed566..8228064b8 100644 --- a/src/test/java/persistence/sql/ddl/DDLHelperTest.java +++ b/src/test/java/persistence/sql/ddl/DDLHelperTest.java @@ -1,6 +1,7 @@ package persistence.sql.ddl; import org.junit.jupiter.api.Test; +import persistence.sql.common.Person; import static org.junit.jupiter.api.Assertions.*; diff --git a/src/test/java/persistence/sql/dml/DMLHelperTest.java b/src/test/java/persistence/sql/dml/DMLHelperTest.java new file mode 100644 index 000000000..876c3cd68 --- /dev/null +++ b/src/test/java/persistence/sql/dml/DMLHelperTest.java @@ -0,0 +1,53 @@ +package persistence.sql.dml; + +import org.junit.jupiter.api.Test; +import persistence.sql.common.Person; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class DMLHelperTest { + + @Test + void Insert_쿼리를_생성한다() { + Person person = new Person(10L, "홍길동", 10, "abc@abc.com", 0); + DMLHelper dmlHelper = new DMLHelper(); + + assertEquals( + "insert into users (nick_name, old, email) values ('홍길동', 10, 'abc@abc.com');", + dmlHelper.getInsertQuery(Person.class, person) + ); + } + + @Test + void FindAllQuery를_생성한다() { + DMLHelper dmlHelper = new DMLHelper(); + + assertEquals( + "select * from users;", + dmlHelper.getFindAllQuery(Person.class) + ); + } + + @Test + void FindByIdQuery를_생성한다() { + DMLHelper dmlHelper = new DMLHelper(); + Long id = 1L; + + assertEquals( + "select * from users where id = 1;", + dmlHelper.getFindByIdQuery(Person.class, id) + ); + } + + @Test + void Delete쿼리를_생성한다() { + DMLHelper dmlHelper = new DMLHelper(); + var person = new Person("홍길동", 30, "test@test.com", 10); + + assertEquals( + "delete from users where nick_name = '홍길동' and old = 30 and email = 'test@test.com';", + dmlHelper.getDeleteQuery(Person.class, person) + ); + } +} \ No newline at end of file