From 3a6d726f4670d6b5f8c8cf64910588e1c2ea934c Mon Sep 17 00:00:00 2001 From: seungnong Date: Tue, 26 Nov 2024 00:17:52 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20SelectQueryBuilder=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20where=20clause=20=EC=A0=81=EC=9A=A9=20=EA=B3=A0?= =?UTF-8?q?=EB=8F=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/persistence/entity/EntityLoader.java | 2 +- .../sql/dml/select/ConditionType.java | 16 ++++ .../sql/dml/select/SelectQueryBuilder.java | 76 ++++++++++++++----- .../sql/dml/select/WhereCondition.java | 27 +++++++ .../dml/select/SelectQueryBuilderTest.java | 4 +- 5 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 src/main/java/persistence/sql/dml/select/ConditionType.java create mode 100644 src/main/java/persistence/sql/dml/select/WhereCondition.java diff --git a/src/main/java/persistence/entity/EntityLoader.java b/src/main/java/persistence/entity/EntityLoader.java index add421dd..8104d64f 100644 --- a/src/main/java/persistence/entity/EntityLoader.java +++ b/src/main/java/persistence/entity/EntityLoader.java @@ -12,7 +12,7 @@ public EntityLoader(JdbcTemplate jdbcTemplate) { } public Object find(Class clazz, Long id) { - String findByIdQuery = SelectQueryBuilder.generateQuery(clazz, id); + String findByIdQuery = SelectQueryBuilder.generateQuery(clazz, id).build(); return jdbcTemplate.queryForObject(findByIdQuery, new EntityRowMapper<>(clazz)); } } diff --git a/src/main/java/persistence/sql/dml/select/ConditionType.java b/src/main/java/persistence/sql/dml/select/ConditionType.java new file mode 100644 index 00000000..99898137 --- /dev/null +++ b/src/main/java/persistence/sql/dml/select/ConditionType.java @@ -0,0 +1,16 @@ +package persistence.sql.dml.select; + +public enum ConditionType { + AND("and"), + OR("or"), + ; + private final String value; + + ConditionType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java index 59627024..2141541c 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java @@ -4,38 +4,78 @@ import persistence.sql.NameUtils; import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; public class SelectQueryBuilder { + private String tableName; + private List whereConditions = new ArrayList<>(); + private SelectQueryBuilder() { } - public static String generateQuery(Class entityClass) { - String tableName = NameUtils.getTableName(entityClass); + private void setTableName(String tableName) { + this.tableName = tableName; + } + + private void addWhereCondition(String columnName, String condition) { + whereConditions.add(new WhereCondition(columnName, condition)); + } + public String build() { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder - .append("select * from ") - .append(tableName) - .append(";"); + stringBuilder.append("select * from ").append(tableName); + + if (whereConditions != null && !whereConditions.isEmpty()) { + String whereClause = getWhereClause(); + stringBuilder.append(whereClause); + } + + stringBuilder.append(";"); return stringBuilder.toString(); } - public static String generateQuery(Class entityClass, Long id) { - String tableName = NameUtils.getTableName(entityClass); - String idColumnName = NameUtils.getColumnName(getIdColumn(entityClass)); - + private String getWhereClause() { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder - .append("select * from ") - .append(tableName) - .append(" where ") - .append(idColumnName) - .append(" = ") - .append(id.toString()) - .append(";"); + stringBuilder.append("where "); + for (WhereCondition whereCondition : whereConditions) { + ConditionType conditionType = whereCondition.getConditionType(); + String columnName = whereCondition.getColumnName(); + List conditions = whereCondition.getConditions(); + if (conditionType != null) { + stringBuilder.append(conditionType.getValue()).append(" "); + } + stringBuilder.append(columnName).append(" "); + + if (conditions.size() == 1) { + stringBuilder.append("= ").append(conditions.get(0)); + } else { + stringBuilder.append("in ("); + conditions.forEach(condition -> stringBuilder.append(condition).append(", ")); + stringBuilder.append(")"); + } + stringBuilder.append(" "); + } + stringBuilder.setLength(stringBuilder.length() - 1); return stringBuilder.toString(); } + public static SelectQueryBuilder generateQuery(Class entityClass) { + SelectQueryBuilder selectQueryBuilder = new SelectQueryBuilder(); + selectQueryBuilder.setTableName( + NameUtils.getTableName(entityClass) + ); + return selectQueryBuilder; + } + + public static SelectQueryBuilder generateQuery(Class entityClass, Long id) { + SelectQueryBuilder selectQueryBuilder = generateQuery(entityClass); + Field idColumn = getIdColumn(entityClass); + String columnName = NameUtils.getColumnName(idColumn); + selectQueryBuilder.addWhereCondition(columnName, id.toString()); + return selectQueryBuilder; + } + private static Field getIdColumn(Class entityClass) { for (Field field : entityClass.getDeclaredFields()) { if (field.isAnnotationPresent(Id.class)) { diff --git a/src/main/java/persistence/sql/dml/select/WhereCondition.java b/src/main/java/persistence/sql/dml/select/WhereCondition.java new file mode 100644 index 00000000..81fdad1f --- /dev/null +++ b/src/main/java/persistence/sql/dml/select/WhereCondition.java @@ -0,0 +1,27 @@ +package persistence.sql.dml.select; + +import java.util.Collections; +import java.util.List; + +public class WhereCondition { + private ConditionType conditionType; + private String columnName; + private List conditions; + + public WhereCondition(String columnName, String condition) { + this.columnName = columnName; + this.conditions = Collections.singletonList(condition); + } + + public ConditionType getConditionType() { + return conditionType; + } + + public String getColumnName() { + return columnName; + } + + public List getConditions() { + return conditions; + } +} \ No newline at end of file diff --git a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java index ea4b00e3..1b09528a 100644 --- a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java +++ b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java @@ -13,7 +13,7 @@ public class SelectQueryBuilderTest { @DisplayName("FindAll query 테스트") void findAllQueryTest() { Class personClass = Person.class; - String selectQuery = SelectQueryBuilder.generateQuery(personClass); + String selectQuery = SelectQueryBuilder.generateQuery(personClass).build(); logger.debug("query : {}", selectQuery); } @@ -23,7 +23,7 @@ void findAllQueryTest() { void findByIdTest() { Class personClass = Person.class; - String selectQuery = SelectQueryBuilder.generateQuery(personClass, 1L); + String selectQuery = SelectQueryBuilder.generateQuery(personClass, 1L).build(); logger.debug("query : {}", selectQuery); } From 96a28c546b5afa5789c5c79ef2225160c049222f Mon Sep 17 00:00:00 2001 From: seungnong Date: Wed, 27 Nov 2024 01:31:16 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20SelectQueryBuilder=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EA=B0=9C=ED=8E=B8,=20EntityLoader=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20Find=20with=20join=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/persistence/entity/EntityLoader.java | 18 +++- .../java/persistence/entity/EntityUtils.java | 13 +++ src/main/java/persistence/sql/NameUtils.java | 5 + .../persistence/sql/component/ColumnInfo.java | 29 ++++++ .../persistence/sql/component/Condition.java | 39 ++++++++ .../sql/component/ConditionBuilder.java | 41 ++++++++ .../sql/component/JoinCondition.java | 31 ++++++ .../sql/component/JoinConditionBuilder.java | 32 ++++++ .../persistence/sql/component/JoinType.java | 16 +++ .../persistence/sql/component/TableInfo.java | 21 ++++ .../sql/dml/select/ConditionType.java | 16 --- .../sql/dml/select/SelectQuery.java | 98 +++++++++++++++++++ .../sql/dml/select/SelectQueryBuilder.java | 91 ++++------------- .../sql/dml/select/WhereCondition.java | 27 ----- .../dml/select/SelectQueryBuilderTest.java | 77 ++++++++++++++- 15 files changed, 433 insertions(+), 121 deletions(-) create mode 100644 src/main/java/persistence/sql/component/ColumnInfo.java create mode 100644 src/main/java/persistence/sql/component/Condition.java create mode 100644 src/main/java/persistence/sql/component/ConditionBuilder.java create mode 100644 src/main/java/persistence/sql/component/JoinCondition.java create mode 100644 src/main/java/persistence/sql/component/JoinConditionBuilder.java create mode 100644 src/main/java/persistence/sql/component/JoinType.java create mode 100644 src/main/java/persistence/sql/component/TableInfo.java delete mode 100644 src/main/java/persistence/sql/dml/select/ConditionType.java create mode 100644 src/main/java/persistence/sql/dml/select/SelectQuery.java delete mode 100644 src/main/java/persistence/sql/dml/select/WhereCondition.java diff --git a/src/main/java/persistence/entity/EntityLoader.java b/src/main/java/persistence/entity/EntityLoader.java index 8104d64f..1cf9df0d 100644 --- a/src/main/java/persistence/entity/EntityLoader.java +++ b/src/main/java/persistence/entity/EntityLoader.java @@ -2,8 +2,13 @@ import jdbc.EntityRowMapper; import jdbc.JdbcTemplate; +import persistence.sql.component.ConditionBuilder; +import persistence.sql.component.TableInfo; +import persistence.sql.dml.select.SelectQuery; import persistence.sql.dml.select.SelectQueryBuilder; +import java.util.Collections; + public class EntityLoader { private final JdbcTemplate jdbcTemplate; @@ -12,7 +17,16 @@ public EntityLoader(JdbcTemplate jdbcTemplate) { } public Object find(Class clazz, Long id) { - String findByIdQuery = SelectQueryBuilder.generateQuery(clazz, id).build(); - return jdbcTemplate.queryForObject(findByIdQuery, new EntityRowMapper<>(clazz)); + SelectQuery selectQuery = new SelectQueryBuilder() + .fromTableInfo(new TableInfo(clazz)) + .whereCondition( + new ConditionBuilder() + .columnInfo(EntityUtils.getIdColumn(clazz)) + .values(Collections.singletonList(id.toString())) + .build() + ) + .build(); + String query = selectQuery.toString(); + return jdbcTemplate.queryForObject(query, new EntityRowMapper<>(clazz)); } } diff --git a/src/main/java/persistence/entity/EntityUtils.java b/src/main/java/persistence/entity/EntityUtils.java index 153fa0cb..7bedd87e 100644 --- a/src/main/java/persistence/entity/EntityUtils.java +++ b/src/main/java/persistence/entity/EntityUtils.java @@ -4,11 +4,24 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Transient; +import persistence.sql.component.ColumnInfo; +import persistence.sql.component.TableInfo; import java.lang.reflect.Field; import java.util.Arrays; public class EntityUtils { + public static ColumnInfo getIdColumn(Class clazz) { + TableInfo tableInfo = new TableInfo(clazz); + + Field[] declaredFields = clazz.getDeclaredFields(); + Field idField = Arrays.stream(declaredFields) + .filter(field -> field.isAnnotationPresent(Id.class)) + .findAny() + .orElseThrow(); + return new ColumnInfo(tableInfo, idField); + } + public static Long getIdValue(Object entity) { Field[] declaredFields = entity.getClass().getDeclaredFields(); Field idField = Arrays.stream(declaredFields) diff --git a/src/main/java/persistence/sql/NameUtils.java b/src/main/java/persistence/sql/NameUtils.java index 95f12c0d..7b214ddf 100644 --- a/src/main/java/persistence/sql/NameUtils.java +++ b/src/main/java/persistence/sql/NameUtils.java @@ -1,6 +1,7 @@ package persistence.sql; import jakarta.persistence.Column; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; import java.lang.reflect.Field; @@ -12,6 +13,10 @@ public static String getColumnName(Field field) { && !"".equals(field.getAnnotation(Column.class).name())) { return field.getAnnotation(Column.class).name(); } + if (field.isAnnotationPresent(JoinColumn.class) + && !"".equals(field.getAnnotation(JoinColumn.class).name())) { + return field.getAnnotation(JoinColumn.class).name(); + } return field.getName(); } diff --git a/src/main/java/persistence/sql/component/ColumnInfo.java b/src/main/java/persistence/sql/component/ColumnInfo.java new file mode 100644 index 00000000..22266f8d --- /dev/null +++ b/src/main/java/persistence/sql/component/ColumnInfo.java @@ -0,0 +1,29 @@ +package persistence.sql.component; + +import persistence.sql.NameUtils; + +import java.lang.reflect.Field; + +public class ColumnInfo { + private TableInfo tableInfo; + private Class columnType; + private String columnName; + + public ColumnInfo(TableInfo tableInfo, Field field) { + this.tableInfo = tableInfo; + this.columnType = field.getType(); + this.columnName = NameUtils.getColumnName(field); + } + + public TableInfo getTableInfo() { + return tableInfo; + } + + public Class getColumnType() { + return columnType; + } + + public String getColumnName() { + return columnName; + } +} diff --git a/src/main/java/persistence/sql/component/Condition.java b/src/main/java/persistence/sql/component/Condition.java new file mode 100644 index 00000000..cbdccf66 --- /dev/null +++ b/src/main/java/persistence/sql/component/Condition.java @@ -0,0 +1,39 @@ +package persistence.sql.component; + +import java.util.List; + +public class Condition { + private ColumnInfo columnInfo; + private List values; + private Condition andCondition; + private Condition orCondition; + + public ColumnInfo getColumnInfo() { + return columnInfo; + } + + public List getValues() { + return values; + } + + public Condition getAndCondition() { + return andCondition; + } + + public Condition getOrCondition() { + return orCondition; + } + + public Condition(ColumnInfo columnInfo, List values) { + this.columnInfo = columnInfo; + this.values = values; + } + + public void setAndCondition(Condition andCondition) { + this.andCondition = andCondition; + } + + public void setOrCondition(Condition orCondition) { + this.orCondition = orCondition; + } +} \ No newline at end of file diff --git a/src/main/java/persistence/sql/component/ConditionBuilder.java b/src/main/java/persistence/sql/component/ConditionBuilder.java new file mode 100644 index 00000000..f58ee513 --- /dev/null +++ b/src/main/java/persistence/sql/component/ConditionBuilder.java @@ -0,0 +1,41 @@ +package persistence.sql.component; + +import java.util.List; + +public class ConditionBuilder { + private ColumnInfo columnInfo; + private List values; + private Condition andCondition; + private Condition orCondition; + + public ConditionBuilder columnInfo(ColumnInfo columnInfo) { + this.columnInfo = columnInfo; + return this; + } + + public ConditionBuilder values(List values) { + this.values = values; + return this; + } + + public ConditionBuilder andCondition(Condition andCondition) { + this.andCondition = andCondition; + return this; + } + + public ConditionBuilder orCondition(Condition orCondition) { + this.orCondition = orCondition; + return this; + } + + public Condition build() { + Condition condition = new Condition(columnInfo, values); + if (andCondition != null) { + condition.setAndCondition(andCondition); + } + if (orCondition != null) { + condition.setOrCondition(orCondition); + } + return condition; + } +} diff --git a/src/main/java/persistence/sql/component/JoinCondition.java b/src/main/java/persistence/sql/component/JoinCondition.java new file mode 100644 index 00000000..b542dcdf --- /dev/null +++ b/src/main/java/persistence/sql/component/JoinCondition.java @@ -0,0 +1,31 @@ +package persistence.sql.component; + +public class JoinCondition { + private JoinType joinType; + private TableInfo tableInfo; + private ColumnInfo onConditionColumn1; + private ColumnInfo onConditionColumn2; + + public JoinType getJoinType() { + return joinType; + } + + public TableInfo getTableInfo() { + return tableInfo; + } + + public ColumnInfo getOnConditionColumn1() { + return onConditionColumn1; + } + + public ColumnInfo getOnConditionColumn2() { + return onConditionColumn2; + } + + public JoinCondition(JoinType joinType, TableInfo tableInfo, ColumnInfo onConditionColumn1, ColumnInfo onConditionColumn2) { + this.joinType = joinType; + this.tableInfo = tableInfo; + this.onConditionColumn1 = onConditionColumn1; + this.onConditionColumn2 = onConditionColumn2; + } +} diff --git a/src/main/java/persistence/sql/component/JoinConditionBuilder.java b/src/main/java/persistence/sql/component/JoinConditionBuilder.java new file mode 100644 index 00000000..5fffcd98 --- /dev/null +++ b/src/main/java/persistence/sql/component/JoinConditionBuilder.java @@ -0,0 +1,32 @@ +package persistence.sql.component; + +public class JoinConditionBuilder { + private JoinType joinType; + private TableInfo tableInfo; + private ColumnInfo onConditionColumn1; + private ColumnInfo onConditionColumn2; + + public JoinConditionBuilder joinType(JoinType joinType) { + this.joinType = joinType; + return this; + } + + public JoinConditionBuilder tableInfo(TableInfo tableInfo) { + this.tableInfo = tableInfo; + return this; + } + + public JoinConditionBuilder onConditionColumn1(ColumnInfo onConditionColumn1) { + this.onConditionColumn1 = onConditionColumn1; + return this; + } + + public JoinConditionBuilder onConditionColumn2(ColumnInfo onConditionColumn2) { + this.onConditionColumn2 = onConditionColumn2; + return this; + } + + public JoinCondition build() { + return new JoinCondition(joinType, tableInfo, onConditionColumn1, onConditionColumn2); + } +} diff --git a/src/main/java/persistence/sql/component/JoinType.java b/src/main/java/persistence/sql/component/JoinType.java new file mode 100644 index 00000000..9c936c59 --- /dev/null +++ b/src/main/java/persistence/sql/component/JoinType.java @@ -0,0 +1,16 @@ +package persistence.sql.component; + +public enum JoinType { + INNER_JOIN("inner join"), + LEFT_JOIN("left join"), + /* TODO */; + private String value; + + JoinType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/persistence/sql/component/TableInfo.java b/src/main/java/persistence/sql/component/TableInfo.java new file mode 100644 index 00000000..e384fae1 --- /dev/null +++ b/src/main/java/persistence/sql/component/TableInfo.java @@ -0,0 +1,21 @@ +package persistence.sql.component; + +import persistence.sql.NameUtils; + +public class TableInfo { + private Class tableType; + private String tableName; + + public TableInfo(Class tableType) { + this.tableType = tableType; + this.tableName = NameUtils.getTableName(tableType); + } + + public Class getTableType() { + return tableType; + } + + public String getTableName() { + return tableName; + } +} diff --git a/src/main/java/persistence/sql/dml/select/ConditionType.java b/src/main/java/persistence/sql/dml/select/ConditionType.java deleted file mode 100644 index 99898137..00000000 --- a/src/main/java/persistence/sql/dml/select/ConditionType.java +++ /dev/null @@ -1,16 +0,0 @@ -package persistence.sql.dml.select; - -public enum ConditionType { - AND("and"), - OR("or"), - ; - private final String value; - - ConditionType(String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} diff --git a/src/main/java/persistence/sql/dml/select/SelectQuery.java b/src/main/java/persistence/sql/dml/select/SelectQuery.java new file mode 100644 index 00000000..41e853be --- /dev/null +++ b/src/main/java/persistence/sql/dml/select/SelectQuery.java @@ -0,0 +1,98 @@ +package persistence.sql.dml.select; + +import persistence.sql.component.ColumnInfo; +import persistence.sql.component.Condition; +import persistence.sql.component.JoinCondition; +import persistence.sql.component.TableInfo; + +import java.util.List; + +public class SelectQuery { + private TableInfo fromTableInfo; + private Condition whereCondition; + private List joinConditions; + + public SelectQuery(TableInfo fromTableInfo, Condition whereCondition) { + this.fromTableInfo = fromTableInfo; + this.whereCondition = whereCondition; + } + + public void setJoinConditions(List joinConditions) { + this.joinConditions = joinConditions; + } + + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append("select * from ") + .append(fromTableInfo.getTableName()) + .append(" "); + if (whereCondition != null) { + stringBuilder.append(getWhereClause()); + } + if (joinConditions != null) { + stringBuilder.append(getJoinClauses()); + } + + stringBuilder.setLength(stringBuilder.length() - 1); + stringBuilder.append(";"); + return stringBuilder.toString(); + } + + private String getWhereClause() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append("where ") + .append(whereCondition.getColumnInfo().getColumnName()) + .append(" "); + + List values = whereCondition.getValues(); + if (values.isEmpty()) { + stringBuilder.append("= null"); + } else if (values.size() == 1) { + stringBuilder.append("= ").append(values.get(0)); + } else { + stringBuilder.append("in ("); + values.forEach(value -> stringBuilder.append(value).append(", ")); + stringBuilder.setLength(stringBuilder.length() - 2); + stringBuilder.append(")"); + } + /* todo : andCondition and orCondition */ + stringBuilder.append(" "); + return stringBuilder.toString(); + } + + private String getJoinClauses() { + StringBuilder stringBuilder = new StringBuilder(); + joinConditions.forEach( + joinCondition -> stringBuilder.append(getSingleJoinClause(joinCondition)) + ); + return stringBuilder.toString(); + } + + private String getSingleJoinClause(JoinCondition joinCondition) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append(joinCondition.getJoinType().getValue()) + .append(" ") + .append(joinCondition.getTableInfo().getTableName()) + .append(" on "); + ColumnInfo onConditionColumn1 = joinCondition.getOnConditionColumn1(); + String table1Name = onConditionColumn1.getTableInfo().getTableName(); + String column1Name = onConditionColumn1.getColumnName(); + stringBuilder + .append(table1Name) + .append(".") + .append(column1Name) + .append(" = "); + ColumnInfo onConditionColumn2 = joinCondition.getOnConditionColumn2(); + String table2Name = onConditionColumn2.getTableInfo().getTableName(); + String column2Name = onConditionColumn2.getColumnName(); + stringBuilder + .append(table2Name) + .append(".") + .append(column2Name) + .append(" "); + return stringBuilder.toString(); + } +} diff --git a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java index 2141541c..723bc145 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java @@ -1,87 +1,36 @@ package persistence.sql.dml.select; -import jakarta.persistence.Id; -import persistence.sql.NameUtils; +import persistence.sql.component.Condition; +import persistence.sql.component.JoinCondition; +import persistence.sql.component.TableInfo; -import java.lang.reflect.Field; -import java.util.ArrayList; import java.util.List; public class SelectQueryBuilder { - private String tableName; - private List whereConditions = new ArrayList<>(); + private TableInfo fromTableInfo; + private Condition whereCondition; + private List joinConditions; - private SelectQueryBuilder() { + public SelectQueryBuilder fromTableInfo(TableInfo fromTableInfo) { + this.fromTableInfo = fromTableInfo; + return this; } - private void setTableName(String tableName) { - this.tableName = tableName; + public SelectQueryBuilder whereCondition(Condition whereCondition) { + this.whereCondition = whereCondition; + return this; } - private void addWhereCondition(String columnName, String condition) { - whereConditions.add(new WhereCondition(columnName, condition)); + public SelectQueryBuilder joinConditions(List joinConditions) { + this.joinConditions = joinConditions; + return this; } - public String build() { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("select * from ").append(tableName); - - if (whereConditions != null && !whereConditions.isEmpty()) { - String whereClause = getWhereClause(); - stringBuilder.append(whereClause); - } - - stringBuilder.append(";"); - return stringBuilder.toString(); - } - - private String getWhereClause() { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("where "); - for (WhereCondition whereCondition : whereConditions) { - ConditionType conditionType = whereCondition.getConditionType(); - String columnName = whereCondition.getColumnName(); - List conditions = whereCondition.getConditions(); - if (conditionType != null) { - stringBuilder.append(conditionType.getValue()).append(" "); - } - stringBuilder.append(columnName).append(" "); - - if (conditions.size() == 1) { - stringBuilder.append("= ").append(conditions.get(0)); - } else { - stringBuilder.append("in ("); - conditions.forEach(condition -> stringBuilder.append(condition).append(", ")); - stringBuilder.append(")"); - } - stringBuilder.append(" "); - } - stringBuilder.setLength(stringBuilder.length() - 1); - return stringBuilder.toString(); - } - - public static SelectQueryBuilder generateQuery(Class entityClass) { - SelectQueryBuilder selectQueryBuilder = new SelectQueryBuilder(); - selectQueryBuilder.setTableName( - NameUtils.getTableName(entityClass) - ); - return selectQueryBuilder; - } - - public static SelectQueryBuilder generateQuery(Class entityClass, Long id) { - SelectQueryBuilder selectQueryBuilder = generateQuery(entityClass); - Field idColumn = getIdColumn(entityClass); - String columnName = NameUtils.getColumnName(idColumn); - selectQueryBuilder.addWhereCondition(columnName, id.toString()); - return selectQueryBuilder; - } - - private static Field getIdColumn(Class entityClass) { - for (Field field : entityClass.getDeclaredFields()) { - if (field.isAnnotationPresent(Id.class)) { - return field; - } + public SelectQuery build() { + SelectQuery selectQuery = new SelectQuery(fromTableInfo, whereCondition); + if (joinConditions != null) { + selectQuery.setJoinConditions(joinConditions); } - throw new IllegalArgumentException("Inappropriate entity class!"); + return selectQuery; } } diff --git a/src/main/java/persistence/sql/dml/select/WhereCondition.java b/src/main/java/persistence/sql/dml/select/WhereCondition.java deleted file mode 100644 index 81fdad1f..00000000 --- a/src/main/java/persistence/sql/dml/select/WhereCondition.java +++ /dev/null @@ -1,27 +0,0 @@ -package persistence.sql.dml.select; - -import java.util.Collections; -import java.util.List; - -public class WhereCondition { - private ConditionType conditionType; - private String columnName; - private List conditions; - - public WhereCondition(String columnName, String condition) { - this.columnName = columnName; - this.conditions = Collections.singletonList(condition); - } - - public ConditionType getConditionType() { - return conditionType; - } - - public String getColumnName() { - return columnName; - } - - public List getConditions() { - return conditions; - } -} \ No newline at end of file diff --git a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java index 1b09528a..af8872f2 100644 --- a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java +++ b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java @@ -1,10 +1,23 @@ package persistence.sql.dml.select; +import example.entity.Order; +import example.entity.OrderItem; import example.entity.Person; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import persistence.entity.EntityUtils; +import persistence.sql.component.ColumnInfo; +import persistence.sql.component.ConditionBuilder; +import persistence.sql.component.JoinConditionBuilder; +import persistence.sql.component.JoinType; +import persistence.sql.component.TableInfo; + +import java.util.Arrays; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; public class SelectQueryBuilderTest { private static final Logger logger = LoggerFactory.getLogger(SelectQueryBuilderTest.class); @@ -13,18 +26,72 @@ public class SelectQueryBuilderTest { @DisplayName("FindAll query 테스트") void findAllQueryTest() { Class personClass = Person.class; - String selectQuery = SelectQueryBuilder.generateQuery(personClass).build(); - - logger.debug("query : {}", selectQuery); + SelectQuery selectQuery = new SelectQueryBuilder() + .fromTableInfo(new TableInfo(personClass)) + .build(); + String query = selectQuery.toString(); + logger.debug(query); + assertThat(query).isEqualTo("select * from users;"); } @Test @DisplayName("FindById query 테스트") void findByIdTest() { Class personClass = Person.class; + SelectQuery selectQuery = new SelectQueryBuilder() + .fromTableInfo(new TableInfo(personClass)) + .whereCondition( + new ConditionBuilder() + .columnInfo(EntityUtils.getIdColumn(personClass)) + .values(Collections.singletonList("1")) + .build() + ) + .build(); + String query = selectQuery.toString(); + logger.debug(query); + assertThat(query).isEqualTo("select * from users where id = 1;"); + } + + @Test + @DisplayName("Find with join test") + void findWithJoinTest() { + Class orderClass = Order.class; + Class orderItemClass = OrderItem.class; + + TableInfo orderTableInfo = new TableInfo(orderClass); + TableInfo orderItemTableInfo = new TableInfo(orderItemClass); + + ColumnInfo orderDotOrderId = new ColumnInfo( + orderTableInfo, + Arrays.stream(orderClass.getDeclaredFields()) + .filter(field -> field.getName().equals("orderItems")) + .findAny() + .get() + ); + ColumnInfo orderItemDotId = EntityUtils.getIdColumn(orderItemClass); - String selectQuery = SelectQueryBuilder.generateQuery(personClass, 1L).build(); + SelectQuery selectQuery = new SelectQueryBuilder() + .fromTableInfo(orderTableInfo) + .whereCondition( + new ConditionBuilder() + .columnInfo(EntityUtils.getIdColumn(orderClass)) + .values(Collections.singletonList("1")) + .build() + ) + .joinConditions( + Collections.singletonList( + new JoinConditionBuilder() + .joinType(JoinType.INNER_JOIN) + .tableInfo(orderItemTableInfo) + .onConditionColumn1(orderDotOrderId) + .onConditionColumn2(orderItemDotId) + .build() + ) + ) + .build(); - logger.debug("query : {}", selectQuery); + String query = selectQuery.toString(); + logger.debug(query); + assertThat(query).isEqualTo("select * from orders where id = 1 inner join order_items on orders.order_id = order_items.id;"); } } From c9e77a152a438f0574785c45d9357b0ac4d1e8ee Mon Sep 17 00:00:00 2001 From: seungnong Date: Sat, 30 Nov 2024 10:01:58 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20Order,=20OrderItem=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/example/entity/Order.java | 24 +++++++++++++++++++++ src/main/java/example/entity/OrderItem.java | 17 +++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/main/java/example/entity/Order.java create mode 100644 src/main/java/example/entity/OrderItem.java diff --git a/src/main/java/example/entity/Order.java b/src/main/java/example/entity/Order.java new file mode 100644 index 00000000..79c1c49c --- /dev/null +++ b/src/main/java/example/entity/Order.java @@ -0,0 +1,24 @@ +package example.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import java.util.List; + +@Entity +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String orderNumber; + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn(name = "order_id") + private List orderItems; +} diff --git a/src/main/java/example/entity/OrderItem.java b/src/main/java/example/entity/OrderItem.java new file mode 100644 index 00000000..84cdf707 --- /dev/null +++ b/src/main/java/example/entity/OrderItem.java @@ -0,0 +1,17 @@ +package example.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "order_items") +public class OrderItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String product; + private Integer quantity; +} From 31481a2093243c0e58855069cb91e4ace4910277 Mon Sep 17 00:00:00 2001 From: seungnong Date: Sat, 30 Nov 2024 10:12:34 +0900 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20JoinCondition=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/sql/component/ColumnInfo.java | 4 ---- .../sql/component/JoinCondition.java | 16 +++++++-------- .../sql/dml/select/SelectQuery.java | 20 +++++++++---------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/main/java/persistence/sql/component/ColumnInfo.java b/src/main/java/persistence/sql/component/ColumnInfo.java index 22266f8d..2a34a69f 100644 --- a/src/main/java/persistence/sql/component/ColumnInfo.java +++ b/src/main/java/persistence/sql/component/ColumnInfo.java @@ -19,10 +19,6 @@ public TableInfo getTableInfo() { return tableInfo; } - public Class getColumnType() { - return columnType; - } - public String getColumnName() { return columnName; } diff --git a/src/main/java/persistence/sql/component/JoinCondition.java b/src/main/java/persistence/sql/component/JoinCondition.java index b542dcdf..9d562c1a 100644 --- a/src/main/java/persistence/sql/component/JoinCondition.java +++ b/src/main/java/persistence/sql/component/JoinCondition.java @@ -3,8 +3,8 @@ public class JoinCondition { private JoinType joinType; private TableInfo tableInfo; - private ColumnInfo onConditionColumn1; - private ColumnInfo onConditionColumn2; + private ColumnInfo sourceColumnInfo; + private ColumnInfo targetColumnInfo; public JoinType getJoinType() { return joinType; @@ -14,18 +14,18 @@ public TableInfo getTableInfo() { return tableInfo; } - public ColumnInfo getOnConditionColumn1() { - return onConditionColumn1; + public ColumnInfo getSourceColumnInfo() { + return sourceColumnInfo; } - public ColumnInfo getOnConditionColumn2() { - return onConditionColumn2; + public ColumnInfo getTargetColumnInfo() { + return targetColumnInfo; } public JoinCondition(JoinType joinType, TableInfo tableInfo, ColumnInfo onConditionColumn1, ColumnInfo onConditionColumn2) { this.joinType = joinType; this.tableInfo = tableInfo; - this.onConditionColumn1 = onConditionColumn1; - this.onConditionColumn2 = onConditionColumn2; + this.sourceColumnInfo = onConditionColumn1; + this.targetColumnInfo = onConditionColumn2; } } diff --git a/src/main/java/persistence/sql/dml/select/SelectQuery.java b/src/main/java/persistence/sql/dml/select/SelectQuery.java index 41e853be..f8e05631 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQuery.java +++ b/src/main/java/persistence/sql/dml/select/SelectQuery.java @@ -77,21 +77,21 @@ private String getSingleJoinClause(JoinCondition joinCondition) { .append(" ") .append(joinCondition.getTableInfo().getTableName()) .append(" on "); - ColumnInfo onConditionColumn1 = joinCondition.getOnConditionColumn1(); - String table1Name = onConditionColumn1.getTableInfo().getTableName(); - String column1Name = onConditionColumn1.getColumnName(); + ColumnInfo sourceColumnInfo = joinCondition.getSourceColumnInfo(); + String sourceTableName = sourceColumnInfo.getTableInfo().getTableName(); + String sourceColumnName = sourceColumnInfo.getColumnName(); stringBuilder - .append(table1Name) + .append(sourceTableName) .append(".") - .append(column1Name) + .append(sourceColumnName) .append(" = "); - ColumnInfo onConditionColumn2 = joinCondition.getOnConditionColumn2(); - String table2Name = onConditionColumn2.getTableInfo().getTableName(); - String column2Name = onConditionColumn2.getColumnName(); + ColumnInfo targetColumnInfo = joinCondition.getTargetColumnInfo(); + String targetTableName = targetColumnInfo.getTableInfo().getTableName(); + String targetColumnName = targetColumnInfo.getColumnName(); stringBuilder - .append(table2Name) + .append(targetTableName) .append(".") - .append(column2Name) + .append(targetColumnName) .append(" "); return stringBuilder.toString(); } From ec3710c16994535c6973d35f8006d16869a7cde1 Mon Sep 17 00:00:00 2001 From: seungnong Date: Sat, 30 Nov 2024 12:16:09 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20select=20join=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/sql/component/ColumnInfo.java | 6 ++++-- .../persistence/sql/dml/select/SelectQuery.java | 16 +++++----------- .../sql/dml/select/SelectQueryBuilderTest.java | 4 ++-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/java/persistence/sql/component/ColumnInfo.java b/src/main/java/persistence/sql/component/ColumnInfo.java index 2a34a69f..3e57d292 100644 --- a/src/main/java/persistence/sql/component/ColumnInfo.java +++ b/src/main/java/persistence/sql/component/ColumnInfo.java @@ -6,12 +6,10 @@ public class ColumnInfo { private TableInfo tableInfo; - private Class columnType; private String columnName; public ColumnInfo(TableInfo tableInfo, Field field) { this.tableInfo = tableInfo; - this.columnType = field.getType(); this.columnName = NameUtils.getColumnName(field); } @@ -22,4 +20,8 @@ public TableInfo getTableInfo() { public String getColumnName() { return columnName; } + + public String getFullName() { + return tableInfo.getTableName() + "." + columnName; + } } diff --git a/src/main/java/persistence/sql/dml/select/SelectQuery.java b/src/main/java/persistence/sql/dml/select/SelectQuery.java index f8e05631..eddab9f5 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQuery.java +++ b/src/main/java/persistence/sql/dml/select/SelectQuery.java @@ -41,9 +41,11 @@ public String toString() { private String getWhereClause() { StringBuilder stringBuilder = new StringBuilder(); + ColumnInfo columnInfo = whereCondition.getColumnInfo(); + stringBuilder .append("where ") - .append(whereCondition.getColumnInfo().getColumnName()) + .append(columnInfo.getFullName()) .append(" "); List values = whereCondition.getValues(); @@ -78,20 +80,12 @@ private String getSingleJoinClause(JoinCondition joinCondition) { .append(joinCondition.getTableInfo().getTableName()) .append(" on "); ColumnInfo sourceColumnInfo = joinCondition.getSourceColumnInfo(); - String sourceTableName = sourceColumnInfo.getTableInfo().getTableName(); - String sourceColumnName = sourceColumnInfo.getColumnName(); stringBuilder - .append(sourceTableName) - .append(".") - .append(sourceColumnName) + .append(sourceColumnInfo.getFullName()) .append(" = "); ColumnInfo targetColumnInfo = joinCondition.getTargetColumnInfo(); - String targetTableName = targetColumnInfo.getTableInfo().getTableName(); - String targetColumnName = targetColumnInfo.getColumnName(); stringBuilder - .append(targetTableName) - .append(".") - .append(targetColumnName) + .append(targetColumnInfo.getFullName()) .append(" "); return stringBuilder.toString(); } diff --git a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java index af8872f2..5432a1af 100644 --- a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java +++ b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java @@ -49,7 +49,7 @@ void findByIdTest() { .build(); String query = selectQuery.toString(); logger.debug(query); - assertThat(query).isEqualTo("select * from users where id = 1;"); + assertThat(query).isEqualTo("select * from users where users.id = 1;"); } @Test @@ -92,6 +92,6 @@ void findWithJoinTest() { String query = selectQuery.toString(); logger.debug(query); - assertThat(query).isEqualTo("select * from orders where id = 1 inner join order_items on orders.order_id = order_items.id;"); + assertThat(query).isEqualTo("select * from orders where orders.id = 1 inner join order_items on orders.order_id = order_items.id;"); } } From 9f909968178d0aa135df433ab0aa0357999d88f7 Mon Sep 17 00:00:00 2001 From: seungnong Date: Sat, 30 Nov 2024 22:21:03 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20select=20=EC=A0=88=20column=20?= =?UTF-8?q?=EB=B3=84=20=EC=A1=B0=ED=9A=8C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/persistence/entity/EntityLoader.java | 8 ++-- .../persistence/entity/EntityManagerImpl.java | 6 +-- .../java/persistence/entity/EntityUtils.java | 2 +- .../persistence/sql/component/TableInfo.java | 6 ++- .../sql/dml/select/SelectQuery.java | 38 +++++++++++++++++-- .../sql/dml/select/SelectQueryBuilder.java | 10 +++++ .../dml/select/SelectQueryBuilderTest.java | 8 ++-- 7 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/main/java/persistence/entity/EntityLoader.java b/src/main/java/persistence/entity/EntityLoader.java index 1cf9df0d..102ce8dd 100644 --- a/src/main/java/persistence/entity/EntityLoader.java +++ b/src/main/java/persistence/entity/EntityLoader.java @@ -16,17 +16,17 @@ public EntityLoader(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - public Object find(Class clazz, Long id) { + public Object find(Class entityClass, Long id) { SelectQuery selectQuery = new SelectQueryBuilder() - .fromTableInfo(new TableInfo(clazz)) + .fromTableInfo(TableInfo.from(entityClass)) .whereCondition( new ConditionBuilder() - .columnInfo(EntityUtils.getIdColumn(clazz)) + .columnInfo(EntityUtils.getIdColumn(entityClass)) .values(Collections.singletonList(id.toString())) .build() ) .build(); String query = selectQuery.toString(); - return jdbcTemplate.queryForObject(query, new EntityRowMapper<>(clazz)); + return jdbcTemplate.queryForObject(query, new EntityRowMapper<>(entityClass)); } } diff --git a/src/main/java/persistence/entity/EntityManagerImpl.java b/src/main/java/persistence/entity/EntityManagerImpl.java index ac7705ba..86c7e010 100644 --- a/src/main/java/persistence/entity/EntityManagerImpl.java +++ b/src/main/java/persistence/entity/EntityManagerImpl.java @@ -44,19 +44,19 @@ private void executeEntityActions(List entryList) { } @Override - public Object find(Class clazz, Long id) { + public Object find(Class entityClass, Long id) { if (id == null) { throw new IllegalArgumentException("Unable to find without an ID!"); } /* 관리 중인 엔티티라면 영속성 컨텍스트에서 꺼내서 반환 */ - Object managedEntity = persistenceContext.getEntity(clazz, id); + Object managedEntity = persistenceContext.getEntity(entityClass, id); if (managedEntity != null) { return managedEntity; } /* 관리 중이지 않다면 DB 조회 */ - Object entity = entityLoader.find(clazz, id); + Object entity = entityLoader.find(entityClass, id); persistenceContext.putEntity(entity); return entity; } diff --git a/src/main/java/persistence/entity/EntityUtils.java b/src/main/java/persistence/entity/EntityUtils.java index 7bedd87e..2c5d54b1 100644 --- a/src/main/java/persistence/entity/EntityUtils.java +++ b/src/main/java/persistence/entity/EntityUtils.java @@ -12,7 +12,7 @@ public class EntityUtils { public static ColumnInfo getIdColumn(Class clazz) { - TableInfo tableInfo = new TableInfo(clazz); + TableInfo tableInfo = TableInfo.from(clazz); Field[] declaredFields = clazz.getDeclaredFields(); Field idField = Arrays.stream(declaredFields) diff --git a/src/main/java/persistence/sql/component/TableInfo.java b/src/main/java/persistence/sql/component/TableInfo.java index e384fae1..0605de4a 100644 --- a/src/main/java/persistence/sql/component/TableInfo.java +++ b/src/main/java/persistence/sql/component/TableInfo.java @@ -6,7 +6,11 @@ public class TableInfo { private Class tableType; private String tableName; - public TableInfo(Class tableType) { + public static TableInfo from(Class tableType) { + return new TableInfo(tableType); + } + + private TableInfo(Class tableType) { this.tableType = tableType; this.tableName = NameUtils.getTableName(tableType); } diff --git a/src/main/java/persistence/sql/dml/select/SelectQuery.java b/src/main/java/persistence/sql/dml/select/SelectQuery.java index eddab9f5..c1308ac2 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQuery.java +++ b/src/main/java/persistence/sql/dml/select/SelectQuery.java @@ -8,6 +8,7 @@ import java.util.List; public class SelectQuery { + private List selectColumnInfos; private TableInfo fromTableInfo; private Condition whereCondition; private List joinConditions; @@ -17,6 +18,10 @@ public SelectQuery(TableInfo fromTableInfo, Condition whereCondition) { this.whereCondition = whereCondition; } + public void setSelectColumnInfos(List selectColumnInfos) { + this.selectColumnInfos = selectColumnInfos; + } + public void setJoinConditions(List joinConditions) { this.joinConditions = joinConditions; } @@ -24,9 +29,8 @@ public void setJoinConditions(List joinConditions) { public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder - .append("select * from ") - .append(fromTableInfo.getTableName()) - .append(" "); + .append(getSelectClause()) + .append(getFromClause()); if (whereCondition != null) { stringBuilder.append(getWhereClause()); } @@ -39,6 +43,34 @@ public String toString() { return stringBuilder.toString(); } + private String getSelectClause() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("select "); + + if (selectColumnInfos == null) { + stringBuilder.append("* "); + return stringBuilder.toString(); + } + + selectColumnInfos.forEach( + columnInfo -> stringBuilder + .append(columnInfo.getFullName()) + .append(", ") + ); + stringBuilder.setLength(stringBuilder.length() - 2); + stringBuilder.append(" "); + return stringBuilder.toString(); + } + + private String getFromClause() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append("from ") + .append(fromTableInfo.getTableName()) + .append(" "); + return stringBuilder.toString(); + } + private String getWhereClause() { StringBuilder stringBuilder = new StringBuilder(); ColumnInfo columnInfo = whereCondition.getColumnInfo(); diff --git a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java index 723bc145..0111f46b 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java @@ -1,5 +1,6 @@ package persistence.sql.dml.select; +import persistence.sql.component.ColumnInfo; import persistence.sql.component.Condition; import persistence.sql.component.JoinCondition; import persistence.sql.component.TableInfo; @@ -7,10 +8,16 @@ import java.util.List; public class SelectQueryBuilder { + private List selectColumnInfos; private TableInfo fromTableInfo; private Condition whereCondition; private List joinConditions; + public SelectQueryBuilder selectColumnInfos(List selectColumnInfos) { + this.selectColumnInfos = selectColumnInfos; + return this; + } + public SelectQueryBuilder fromTableInfo(TableInfo fromTableInfo) { this.fromTableInfo = fromTableInfo; return this; @@ -28,6 +35,9 @@ public SelectQueryBuilder joinConditions(List joinConditions) { public SelectQuery build() { SelectQuery selectQuery = new SelectQuery(fromTableInfo, whereCondition); + if (selectColumnInfos != null) { + selectQuery.setSelectColumnInfos(selectColumnInfos); + } if (joinConditions != null) { selectQuery.setJoinConditions(joinConditions); } diff --git a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java index 5432a1af..cff28f3a 100644 --- a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java +++ b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java @@ -27,7 +27,7 @@ public class SelectQueryBuilderTest { void findAllQueryTest() { Class personClass = Person.class; SelectQuery selectQuery = new SelectQueryBuilder() - .fromTableInfo(new TableInfo(personClass)) + .fromTableInfo(TableInfo.from(personClass)) .build(); String query = selectQuery.toString(); logger.debug(query); @@ -39,7 +39,7 @@ void findAllQueryTest() { void findByIdTest() { Class personClass = Person.class; SelectQuery selectQuery = new SelectQueryBuilder() - .fromTableInfo(new TableInfo(personClass)) + .fromTableInfo(TableInfo.from(personClass)) .whereCondition( new ConditionBuilder() .columnInfo(EntityUtils.getIdColumn(personClass)) @@ -58,8 +58,8 @@ void findWithJoinTest() { Class orderClass = Order.class; Class orderItemClass = OrderItem.class; - TableInfo orderTableInfo = new TableInfo(orderClass); - TableInfo orderItemTableInfo = new TableInfo(orderItemClass); + TableInfo orderTableInfo = TableInfo.from(orderClass); + TableInfo orderItemTableInfo = TableInfo.from(orderItemClass); ColumnInfo orderDotOrderId = new ColumnInfo( orderTableInfo, From 1931b4aae80abae6ba259ead2d6a7f93b461b56d Mon Sep 17 00:00:00 2001 From: seungnong Date: Tue, 3 Dec 2024 19:55:13 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20EntityUtils=20getIdColumn=20?= =?UTF-8?q?=EC=97=AD=ED=95=A0=20tableInfo=EB=A1=9C=20=EC=9C=84=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/persistence/entity/EntityLoader.java | 8 +++-- .../java/persistence/entity/EntityUtils.java | 13 ------- .../persistence/sql/component/ColumnInfo.java | 6 +++- .../sql/component/JoinConditionBuilder.java | 14 ++++---- .../persistence/sql/component/TableInfo.java | 36 +++++++++++++------ .../dml/select/SelectQueryBuilderTest.java | 26 ++++++-------- 6 files changed, 55 insertions(+), 48 deletions(-) diff --git a/src/main/java/persistence/entity/EntityLoader.java b/src/main/java/persistence/entity/EntityLoader.java index 102ce8dd..45cf985e 100644 --- a/src/main/java/persistence/entity/EntityLoader.java +++ b/src/main/java/persistence/entity/EntityLoader.java @@ -2,6 +2,7 @@ import jdbc.EntityRowMapper; import jdbc.JdbcTemplate; +import persistence.sql.component.ColumnInfo; import persistence.sql.component.ConditionBuilder; import persistence.sql.component.TableInfo; import persistence.sql.dml.select.SelectQuery; @@ -17,11 +18,14 @@ public EntityLoader(JdbcTemplate jdbcTemplate) { } public Object find(Class entityClass, Long id) { + TableInfo fromTable = TableInfo.from(entityClass); + ColumnInfo idColumn = fromTable.getIdColumn(); + SelectQuery selectQuery = new SelectQueryBuilder() - .fromTableInfo(TableInfo.from(entityClass)) + .fromTableInfo(fromTable) .whereCondition( new ConditionBuilder() - .columnInfo(EntityUtils.getIdColumn(entityClass)) + .columnInfo(idColumn) .values(Collections.singletonList(id.toString())) .build() ) diff --git a/src/main/java/persistence/entity/EntityUtils.java b/src/main/java/persistence/entity/EntityUtils.java index 2c5d54b1..153fa0cb 100644 --- a/src/main/java/persistence/entity/EntityUtils.java +++ b/src/main/java/persistence/entity/EntityUtils.java @@ -4,24 +4,11 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Transient; -import persistence.sql.component.ColumnInfo; -import persistence.sql.component.TableInfo; import java.lang.reflect.Field; import java.util.Arrays; public class EntityUtils { - public static ColumnInfo getIdColumn(Class clazz) { - TableInfo tableInfo = TableInfo.from(clazz); - - Field[] declaredFields = clazz.getDeclaredFields(); - Field idField = Arrays.stream(declaredFields) - .filter(field -> field.isAnnotationPresent(Id.class)) - .findAny() - .orElseThrow(); - return new ColumnInfo(tableInfo, idField); - } - public static Long getIdValue(Object entity) { Field[] declaredFields = entity.getClass().getDeclaredFields(); Field idField = Arrays.stream(declaredFields) diff --git a/src/main/java/persistence/sql/component/ColumnInfo.java b/src/main/java/persistence/sql/component/ColumnInfo.java index 3e57d292..7e2665ee 100644 --- a/src/main/java/persistence/sql/component/ColumnInfo.java +++ b/src/main/java/persistence/sql/component/ColumnInfo.java @@ -8,11 +8,15 @@ public class ColumnInfo { private TableInfo tableInfo; private String columnName; - public ColumnInfo(TableInfo tableInfo, Field field) { + private ColumnInfo(TableInfo tableInfo, Field field) { this.tableInfo = tableInfo; this.columnName = NameUtils.getColumnName(field); } + public static ColumnInfo of(TableInfo tableInfo, Field columnField) { + return new ColumnInfo(tableInfo, columnField); + } + public TableInfo getTableInfo() { return tableInfo; } diff --git a/src/main/java/persistence/sql/component/JoinConditionBuilder.java b/src/main/java/persistence/sql/component/JoinConditionBuilder.java index 5fffcd98..4be8d7ab 100644 --- a/src/main/java/persistence/sql/component/JoinConditionBuilder.java +++ b/src/main/java/persistence/sql/component/JoinConditionBuilder.java @@ -3,8 +3,8 @@ public class JoinConditionBuilder { private JoinType joinType; private TableInfo tableInfo; - private ColumnInfo onConditionColumn1; - private ColumnInfo onConditionColumn2; + private ColumnInfo sourceColumnInfo; + private ColumnInfo targetColumnInfo; public JoinConditionBuilder joinType(JoinType joinType) { this.joinType = joinType; @@ -16,17 +16,17 @@ public JoinConditionBuilder tableInfo(TableInfo tableInfo) { return this; } - public JoinConditionBuilder onConditionColumn1(ColumnInfo onConditionColumn1) { - this.onConditionColumn1 = onConditionColumn1; + public JoinConditionBuilder sourceColumnInfo(ColumnInfo sourceColumnInfo) { + this.sourceColumnInfo = sourceColumnInfo; return this; } - public JoinConditionBuilder onConditionColumn2(ColumnInfo onConditionColumn2) { - this.onConditionColumn2 = onConditionColumn2; + public JoinConditionBuilder targetColumnInfo(ColumnInfo targetColumnInfo) { + this.targetColumnInfo = targetColumnInfo; return this; } public JoinCondition build() { - return new JoinCondition(joinType, tableInfo, onConditionColumn1, onConditionColumn2); + return new JoinCondition(joinType, tableInfo, sourceColumnInfo, targetColumnInfo); } } diff --git a/src/main/java/persistence/sql/component/TableInfo.java b/src/main/java/persistence/sql/component/TableInfo.java index 0605de4a..4e15b039 100644 --- a/src/main/java/persistence/sql/component/TableInfo.java +++ b/src/main/java/persistence/sql/component/TableInfo.java @@ -1,25 +1,41 @@ package persistence.sql.component; +import jakarta.persistence.Id; import persistence.sql.NameUtils; +import java.lang.reflect.Field; +import java.util.Arrays; + public class TableInfo { - private Class tableType; + private Class entityClass; private String tableName; - public static TableInfo from(Class tableType) { - return new TableInfo(tableType); - } - - private TableInfo(Class tableType) { - this.tableType = tableType; - this.tableName = NameUtils.getTableName(tableType); + private TableInfo(Class entityClass) { + this.entityClass = entityClass; + this.tableName = NameUtils.getTableName(entityClass); } - public Class getTableType() { - return tableType; + public static TableInfo from(Class entityClass) { + return new TableInfo(entityClass); } public String getTableName() { return tableName; } + + public ColumnInfo getIdColumn() { + Field idColumnField = Arrays.stream(entityClass.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(Id.class)) + .findAny() + .orElseThrow(); + return ColumnInfo.of(this, idColumnField); + } + + public ColumnInfo getColumn(String columnName) { + Field columnField = Arrays.stream(entityClass.getDeclaredFields()) + .filter(field -> columnName.equals(NameUtils.getColumnName(field))) + .findAny() + .orElseThrow(); + return ColumnInfo.of(this, columnField); + } } diff --git a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java index cff28f3a..a6098acf 100644 --- a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java +++ b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java @@ -7,14 +7,12 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import persistence.entity.EntityUtils; import persistence.sql.component.ColumnInfo; import persistence.sql.component.ConditionBuilder; import persistence.sql.component.JoinConditionBuilder; import persistence.sql.component.JoinType; import persistence.sql.component.TableInfo; -import java.util.Arrays; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; @@ -38,11 +36,14 @@ void findAllQueryTest() { @DisplayName("FindById query 테스트") void findByIdTest() { Class personClass = Person.class; + TableInfo personTable = TableInfo.from(personClass); + ColumnInfo idColumn = personTable.getIdColumn(); + SelectQuery selectQuery = new SelectQueryBuilder() - .fromTableInfo(TableInfo.from(personClass)) + .fromTableInfo(personTable) .whereCondition( new ConditionBuilder() - .columnInfo(EntityUtils.getIdColumn(personClass)) + .columnInfo(idColumn) .values(Collections.singletonList("1")) .build() ) @@ -61,20 +62,15 @@ void findWithJoinTest() { TableInfo orderTableInfo = TableInfo.from(orderClass); TableInfo orderItemTableInfo = TableInfo.from(orderItemClass); - ColumnInfo orderDotOrderId = new ColumnInfo( - orderTableInfo, - Arrays.stream(orderClass.getDeclaredFields()) - .filter(field -> field.getName().equals("orderItems")) - .findAny() - .get() - ); - ColumnInfo orderItemDotId = EntityUtils.getIdColumn(orderItemClass); + ColumnInfo orderId = orderTableInfo.getIdColumn(); + ColumnInfo orderOrderId = orderTableInfo.getColumn("order_id"); + ColumnInfo orderItemId = orderItemTableInfo.getIdColumn(); SelectQuery selectQuery = new SelectQueryBuilder() .fromTableInfo(orderTableInfo) .whereCondition( new ConditionBuilder() - .columnInfo(EntityUtils.getIdColumn(orderClass)) + .columnInfo(orderId) .values(Collections.singletonList("1")) .build() ) @@ -83,8 +79,8 @@ void findWithJoinTest() { new JoinConditionBuilder() .joinType(JoinType.INNER_JOIN) .tableInfo(orderItemTableInfo) - .onConditionColumn1(orderDotOrderId) - .onConditionColumn2(orderItemDotId) + .sourceColumnInfo(orderOrderId) + .targetColumnInfo(orderItemId) .build() ) ) From dddd02a48a2eccb915b8126e878f2d91c0ad1924 Mon Sep 17 00:00:00 2001 From: seungnong Date: Tue, 3 Dec 2024 23:08:23 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20join=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20auto=20join=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/example/entity/Order.java | 18 +++++++- src/main/java/example/entity/OrderItem.java | 27 ++++++++++++ src/main/java/jdbc/EntityRowMapper.java | 4 +- src/main/java/jdbc/JdbcTemplate.java | 9 +++- src/main/java/persistence/EntityScanner.java | 9 ++-- .../java/persistence/entity/EntityLoader.java | 42 ++++++++++++++++--- .../java/persistence/entity/EntityUtils.java | 17 ++++++-- src/main/java/persistence/sql/NameUtils.java | 4 -- .../persistence/sql/component/ColumnInfo.java | 10 ++--- .../persistence/sql/component/JoinInfo.java | 31 ++++++++++++++ .../persistence/sql/component/TableInfo.java | 32 ++++++++++++++ .../sql/dml/insert/InsertQueryBuilder.java | 8 +++- .../sql/dml/select/SelectQuery.java | 6 +-- .../sql/dml/select/SelectQueryBuilder.java | 6 +-- .../entity/EntityManagerImplTest.java | 31 ++++++++++++++ .../dml/select/SelectQueryBuilderTest.java | 33 ++++++++------- 16 files changed, 238 insertions(+), 49 deletions(-) create mode 100644 src/main/java/persistence/sql/component/JoinInfo.java diff --git a/src/main/java/example/entity/Order.java b/src/main/java/example/entity/Order.java index 79c1c49c..883bbf8d 100644 --- a/src/main/java/example/entity/Order.java +++ b/src/main/java/example/entity/Order.java @@ -19,6 +19,22 @@ public class Order { private Long id; private String orderNumber; @OneToMany(fetch = FetchType.EAGER) - @JoinColumn(name = "order_id") + @JoinColumn(name = "order_id", referencedColumnName = "order_id") private List orderItems; + + public String getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } + +// public List getOrderItems() { +// return orderItems; +// } +// +// public void setOrderItems(List orderItems) { +// this.orderItems = orderItems; +// } } diff --git a/src/main/java/example/entity/OrderItem.java b/src/main/java/example/entity/OrderItem.java index 84cdf707..c596f802 100644 --- a/src/main/java/example/entity/OrderItem.java +++ b/src/main/java/example/entity/OrderItem.java @@ -1,5 +1,6 @@ package example.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -14,4 +15,30 @@ public class OrderItem { private Long id; private String product; private Integer quantity; + @Column(name = "order_id") + private Long orderId; + + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } } diff --git a/src/main/java/jdbc/EntityRowMapper.java b/src/main/java/jdbc/EntityRowMapper.java index 64bb0135..ddfb635a 100644 --- a/src/main/java/jdbc/EntityRowMapper.java +++ b/src/main/java/jdbc/EntityRowMapper.java @@ -1,5 +1,6 @@ package jdbc; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Transient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,7 +24,8 @@ public T mapRow(ResultSet resultSet) { T entity = this.entityClass.getDeclaredConstructor().newInstance(); for (Field field : this.entityClass.getDeclaredFields()) { - if (field.isAnnotationPresent(Transient.class)) { + if (field.isAnnotationPresent(Transient.class) + || field.isAnnotationPresent(JoinColumn.class)) { continue; } field.setAccessible(true); diff --git a/src/main/java/jdbc/JdbcTemplate.java b/src/main/java/jdbc/JdbcTemplate.java index 198d1a02..6528112d 100644 --- a/src/main/java/jdbc/JdbcTemplate.java +++ b/src/main/java/jdbc/JdbcTemplate.java @@ -1,5 +1,8 @@ package jdbc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; @@ -7,6 +10,7 @@ import java.util.List; public class JdbcTemplate { + private static final Logger logger = LoggerFactory.getLogger(JdbcTemplate.class); private final Connection connection; public JdbcTemplate(final Connection connection) { @@ -19,6 +23,7 @@ public void execute(final String sql) { } catch (Exception e) { throw new RuntimeException(e); } + logger.debug("executed query : {}", sql); } public Long executeAndReturnGeneratedKey(final String sql) { @@ -32,6 +37,7 @@ public Long executeAndReturnGeneratedKey(final String sql) { } catch (Exception e) { throw new RuntimeException(e); } + logger.debug("executed query : {}", sql); return null; } @@ -40,10 +46,11 @@ public T queryForObject(final String sql, final RowMapper rowMapper) { if (results.size() != 1) { throw new RuntimeException("Expected 1 result, got " + results.size()); } + logger.debug("executed query : {}", sql); return results.get(0); } - public List query(final String sql, final RowMapper rowMapper) { + private List query(final String sql, final RowMapper rowMapper) { try (final ResultSet resultSet = connection.prepareStatement(sql).executeQuery()) { final List result = new ArrayList<>(); while (resultSet.next()) { diff --git a/src/main/java/persistence/EntityScanner.java b/src/main/java/persistence/EntityScanner.java index 23d683b6..ea701e62 100644 --- a/src/main/java/persistence/EntityScanner.java +++ b/src/main/java/persistence/EntityScanner.java @@ -1,9 +1,9 @@ package persistence; import jakarta.persistence.Entity; -import jakarta.persistence.Transient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import persistence.entity.EntityUtils; import persistence.sql.NameUtils; import persistence.sql.ddl.create.CreateQueryBuilder; import persistence.sql.ddl.create.component.column.ColumnComponentBuilder; @@ -77,12 +77,9 @@ public List getDdlDropQueries() { } private String generateDdlCreateQuery(Class entityClass) { - Field[] fields = entityClass.getDeclaredFields(); CreateQueryBuilder queryBuilder = CreateQueryBuilder.newInstance(); - for (Field field : fields) { - if (field.isAnnotationPresent(Transient.class)) { - continue; - } + + for (Field field : EntityUtils.getManagedFields(entityClass)) { queryBuilder.add(ColumnComponentBuilder.from(field)); queryBuilder.add(ConstraintComponentBuilder.from(field)); } diff --git a/src/main/java/persistence/entity/EntityLoader.java b/src/main/java/persistence/entity/EntityLoader.java index 45cf985e..f5ad2eba 100644 --- a/src/main/java/persistence/entity/EntityLoader.java +++ b/src/main/java/persistence/entity/EntityLoader.java @@ -4,11 +4,16 @@ import jdbc.JdbcTemplate; import persistence.sql.component.ColumnInfo; import persistence.sql.component.ConditionBuilder; +import persistence.sql.component.JoinCondition; +import persistence.sql.component.JoinConditionBuilder; +import persistence.sql.component.JoinInfo; +import persistence.sql.component.JoinType; import persistence.sql.component.TableInfo; -import persistence.sql.dml.select.SelectQuery; import persistence.sql.dml.select.SelectQueryBuilder; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; public class EntityLoader { private final JdbcTemplate jdbcTemplate; @@ -21,16 +26,41 @@ public Object find(Class entityClass, Long id) { TableInfo fromTable = TableInfo.from(entityClass); ColumnInfo idColumn = fromTable.getIdColumn(); - SelectQuery selectQuery = new SelectQueryBuilder() - .fromTableInfo(fromTable) + SelectQueryBuilder selectQueryBuilder = new SelectQueryBuilder() + .fromTableInfo(fromTable); + + List joinConditions = getJoinConditions(fromTable); + if (!joinConditions.isEmpty()) { + selectQueryBuilder + .joinConditions(joinConditions); + } + + selectQueryBuilder .whereCondition( new ConditionBuilder() .columnInfo(idColumn) .values(Collections.singletonList(id.toString())) .build() - ) - .build(); - String query = selectQuery.toString(); + ); + + String query = selectQueryBuilder.build().toString(); return jdbcTemplate.queryForObject(query, new EntityRowMapper<>(entityClass)); } + + private List getJoinConditions(TableInfo tableInfo) { + List joinConditions = new ArrayList<>(); + + List joinInfos = tableInfo.getJoinInfos(); + for (JoinInfo joinInfo : joinInfos) { + JoinCondition joinCondition = new JoinConditionBuilder() + .joinType(JoinType.LEFT_JOIN) + .tableInfo(joinInfo.getTargetColumnInfo().getTableInfo()) + .sourceColumnInfo(joinInfo.getSourceColumnInfo()) + .targetColumnInfo(joinInfo.getTargetColumnInfo()) + .build(); + joinConditions.add(joinCondition); + } + + return joinConditions; + } } diff --git a/src/main/java/persistence/entity/EntityUtils.java b/src/main/java/persistence/entity/EntityUtils.java index 153fa0cb..1f3e3121 100644 --- a/src/main/java/persistence/entity/EntityUtils.java +++ b/src/main/java/persistence/entity/EntityUtils.java @@ -3,6 +3,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Transient; import java.lang.reflect.Field; @@ -25,15 +26,25 @@ public static Field[] getManagedFields(Class clazz) { .toArray(Field[]::new); } + public static Field[] getManagedFieldsExceptId(Class clazz) { + return Arrays.stream(clazz.getDeclaredFields()) + .filter(EntityUtils::isManagedField) + .filter(field -> !field.isAnnotationPresent(Id.class)) + .toArray(Field[]::new); + } + private static boolean isManagedField(Field field) { if (field.isAnnotationPresent(Transient.class)) { return false; } - if (field.isAnnotationPresent(Id.class) - && field.isAnnotationPresent(GeneratedValue.class) - && GenerationType.IDENTITY.equals(field.getAnnotation(GeneratedValue.class).strategy())) { + if (field.isAnnotationPresent(JoinColumn.class)) { return false; } +// if (field.isAnnotationPresent(Id.class) +// && field.isAnnotationPresent(GeneratedValue.class) +// && GenerationType.IDENTITY.equals(field.getAnnotation(GeneratedValue.class).strategy())) { +// return false; +// } return true; } diff --git a/src/main/java/persistence/sql/NameUtils.java b/src/main/java/persistence/sql/NameUtils.java index 7b214ddf..6cf3699f 100644 --- a/src/main/java/persistence/sql/NameUtils.java +++ b/src/main/java/persistence/sql/NameUtils.java @@ -13,10 +13,6 @@ public static String getColumnName(Field field) { && !"".equals(field.getAnnotation(Column.class).name())) { return field.getAnnotation(Column.class).name(); } - if (field.isAnnotationPresent(JoinColumn.class) - && !"".equals(field.getAnnotation(JoinColumn.class).name())) { - return field.getAnnotation(JoinColumn.class).name(); - } return field.getName(); } diff --git a/src/main/java/persistence/sql/component/ColumnInfo.java b/src/main/java/persistence/sql/component/ColumnInfo.java index 7e2665ee..88f163e0 100644 --- a/src/main/java/persistence/sql/component/ColumnInfo.java +++ b/src/main/java/persistence/sql/component/ColumnInfo.java @@ -6,11 +6,11 @@ public class ColumnInfo { private TableInfo tableInfo; - private String columnName; + private Field columnField; private ColumnInfo(TableInfo tableInfo, Field field) { this.tableInfo = tableInfo; - this.columnName = NameUtils.getColumnName(field); + this.columnField = field; } public static ColumnInfo of(TableInfo tableInfo, Field columnField) { @@ -21,11 +21,11 @@ public TableInfo getTableInfo() { return tableInfo; } - public String getColumnName() { - return columnName; + public Field getColumnField() { + return columnField; } public String getFullName() { - return tableInfo.getTableName() + "." + columnName; + return tableInfo.getTableName() + "." + NameUtils.getColumnName(columnField); } } diff --git a/src/main/java/persistence/sql/component/JoinInfo.java b/src/main/java/persistence/sql/component/JoinInfo.java new file mode 100644 index 00000000..333a55eb --- /dev/null +++ b/src/main/java/persistence/sql/component/JoinInfo.java @@ -0,0 +1,31 @@ +package persistence.sql.component; + +import java.lang.reflect.Field; + +public class JoinInfo { + private Field sourceTableJoinField; + private ColumnInfo sourceColumnInfo; + private ColumnInfo targetColumnInfo; + + private JoinInfo(Field sourceTableJoinField, ColumnInfo sourceColumnInfo, ColumnInfo targetColumnInfo) { + this.sourceTableJoinField = sourceTableJoinField; + this.sourceColumnInfo = sourceColumnInfo; + this.targetColumnInfo = targetColumnInfo; + } + + public static JoinInfo of(Field sourceTableJoinField, ColumnInfo sourceColumnInfo, ColumnInfo targetColumnInfo) { + return new JoinInfo(sourceTableJoinField, sourceColumnInfo, targetColumnInfo); + } + + public Field getSourceTableJoinField() { + return sourceTableJoinField; + } + + public ColumnInfo getSourceColumnInfo() { + return sourceColumnInfo; + } + + public ColumnInfo getTargetColumnInfo() { + return targetColumnInfo; + } +} diff --git a/src/main/java/persistence/sql/component/TableInfo.java b/src/main/java/persistence/sql/component/TableInfo.java index 4e15b039..0fb04fc7 100644 --- a/src/main/java/persistence/sql/component/TableInfo.java +++ b/src/main/java/persistence/sql/component/TableInfo.java @@ -1,10 +1,14 @@ package persistence.sql.component; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import persistence.sql.NameUtils; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Arrays; +import java.util.List; public class TableInfo { private Class entityClass; @@ -38,4 +42,32 @@ public ColumnInfo getColumn(String columnName) { .orElseThrow(); return ColumnInfo.of(this, columnField); } + + public List getJoinInfos() { + return Arrays.stream(entityClass.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(JoinColumn.class)) + .map(this::getJoinInfo) + .toList(); + } + + private JoinInfo getJoinInfo(Field sourceTableJoinField) { + return JoinInfo.of(sourceTableJoinField, this.getIdColumn(), getJoinTargetColumnInfo(sourceTableJoinField)); + } + + private ColumnInfo getJoinTargetColumnInfo(Field field) { + String joinTargetColumnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); + + Type type = field.getGenericType(); + + if (type instanceof ParameterizedType parameterizedType) { + type = parameterizedType.getActualTypeArguments()[0]; + } + + if (type instanceof Class clazz) { + TableInfo joinTargetTableInfo = TableInfo.from((Class) clazz); + return joinTargetTableInfo.getColumn(joinTargetColumnName); + } + + throw new RuntimeException("Unable to cast to appropriate class type!"); + } } diff --git a/src/main/java/persistence/sql/dml/insert/InsertQueryBuilder.java b/src/main/java/persistence/sql/dml/insert/InsertQueryBuilder.java index edb5a200..8b73d2a9 100644 --- a/src/main/java/persistence/sql/dml/insert/InsertQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/insert/InsertQueryBuilder.java @@ -28,7 +28,7 @@ public static String generateQuery(Object entity) { private static String columnClause(Class clazz) { StringBuilder stringBuilder = new StringBuilder(" ("); - Field[] managedFields = EntityUtils.getManagedFields(clazz); + Field[] managedFields = EntityUtils.getManagedFieldsExceptId(clazz); for (Field field : managedFields) { stringBuilder .append(NameUtils.getColumnName(field)) @@ -45,10 +45,14 @@ private static String valueClause(Object entity) { StringBuilder stringBuilder = new StringBuilder("("); Class clazz = entity.getClass(); - Field[] fields = EntityUtils.getManagedFields(clazz); + Field[] fields = EntityUtils.getManagedFieldsExceptId(clazz); for (Field field : fields) { field.setAccessible(true); Object fieldValue = EntityUtils.getFieldValue(field, entity); + if (fieldValue == null) { + stringBuilder.append("null, "); + continue; + } stringBuilder .append("'") .append(fieldValue) diff --git a/src/main/java/persistence/sql/dml/select/SelectQuery.java b/src/main/java/persistence/sql/dml/select/SelectQuery.java index c1308ac2..aa2d1a63 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQuery.java +++ b/src/main/java/persistence/sql/dml/select/SelectQuery.java @@ -31,12 +31,12 @@ public String toString() { stringBuilder .append(getSelectClause()) .append(getFromClause()); - if (whereCondition != null) { - stringBuilder.append(getWhereClause()); - } if (joinConditions != null) { stringBuilder.append(getJoinClauses()); } + if (whereCondition != null) { + stringBuilder.append(getWhereClause()); + } stringBuilder.setLength(stringBuilder.length() - 1); stringBuilder.append(";"); diff --git a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java index 0111f46b..6276c3b8 100644 --- a/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/select/SelectQueryBuilder.java @@ -35,12 +35,12 @@ public SelectQueryBuilder joinConditions(List joinConditions) { public SelectQuery build() { SelectQuery selectQuery = new SelectQuery(fromTableInfo, whereCondition); - if (selectColumnInfos != null) { - selectQuery.setSelectColumnInfos(selectColumnInfos); - } if (joinConditions != null) { selectQuery.setJoinConditions(joinConditions); } + if (selectColumnInfos != null) { + selectQuery.setSelectColumnInfos(selectColumnInfos); + } return selectQuery; } } diff --git a/src/test/java/persistence/entity/EntityManagerImplTest.java b/src/test/java/persistence/entity/EntityManagerImplTest.java index 7b9cc032..0b856411 100644 --- a/src/test/java/persistence/entity/EntityManagerImplTest.java +++ b/src/test/java/persistence/entity/EntityManagerImplTest.java @@ -2,6 +2,8 @@ import database.DatabaseServer; import database.H2; +import example.entity.Order; +import example.entity.OrderItem; import example.entity.Person; import jdbc.JdbcTemplate; import org.junit.jupiter.api.AfterEach; @@ -14,6 +16,7 @@ import persistence.EntityScanner; import java.sql.SQLException; +import java.util.UUID; public class EntityManagerImplTest { private static final Logger logger = LoggerFactory.getLogger(EntityManagerImplTest.class); @@ -168,4 +171,32 @@ void removeInTransactionTest() { EntityLoader entityLoader = new EntityLoader(jdbcTemplate); Assertions.assertThrows(RuntimeException.class, () -> entityLoader.find(Person.class, 1L)); } + + @Test + @DisplayName("test") + void test() { + EntityManagerImpl entityManagerImpl = new EntityManagerImpl(jdbcTemplate); + entityManagerImpl.beginTransaction(); + + Order insertingOrder = new Order(); + insertingOrder.setOrderNumber(UUID.randomUUID().toString()); + + OrderItem insertingOrderItem1 = new OrderItem(); + insertingOrderItem1.setProduct("P-001"); + insertingOrderItem1.setQuantity(10); + insertingOrderItem1.setOrderId(1L); + + OrderItem insertingOrderItem2 = new OrderItem(); + insertingOrderItem2.setProduct("P-002"); + insertingOrderItem2.setQuantity(15); + insertingOrderItem2.setOrderId(1L); + + entityManagerImpl.persist(insertingOrder); + entityManagerImpl.persist(insertingOrderItem1); + entityManagerImpl.persist(insertingOrderItem2); + + Object o = entityManagerImpl.find(Order.class, 1L); + + logger.debug("{}", o); + } } diff --git a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java index a6098acf..8ec97695 100644 --- a/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java +++ b/src/test/java/persistence/sql/dml/select/SelectQueryBuilderTest.java @@ -9,11 +9,15 @@ import org.slf4j.LoggerFactory; import persistence.sql.component.ColumnInfo; import persistence.sql.component.ConditionBuilder; +import persistence.sql.component.JoinCondition; import persistence.sql.component.JoinConditionBuilder; +import persistence.sql.component.JoinInfo; import persistence.sql.component.JoinType; import persistence.sql.component.TableInfo; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -60,34 +64,35 @@ void findWithJoinTest() { Class orderItemClass = OrderItem.class; TableInfo orderTableInfo = TableInfo.from(orderClass); - TableInfo orderItemTableInfo = TableInfo.from(orderItemClass); + + List joinConditions = new ArrayList<>(); + + List joinInfos = orderTableInfo.getJoinInfos(); + for (JoinInfo joinInfo : joinInfos) { + JoinCondition joinCondition = new JoinConditionBuilder() + .joinType(JoinType.LEFT_JOIN) + .tableInfo(joinInfo.getTargetColumnInfo().getTableInfo()) + .sourceColumnInfo(joinInfo.getSourceColumnInfo()) + .targetColumnInfo(joinInfo.getTargetColumnInfo()) + .build(); + joinConditions.add(joinCondition); + } ColumnInfo orderId = orderTableInfo.getIdColumn(); - ColumnInfo orderOrderId = orderTableInfo.getColumn("order_id"); - ColumnInfo orderItemId = orderItemTableInfo.getIdColumn(); SelectQuery selectQuery = new SelectQueryBuilder() .fromTableInfo(orderTableInfo) + .joinConditions(joinConditions) .whereCondition( new ConditionBuilder() .columnInfo(orderId) .values(Collections.singletonList("1")) .build() ) - .joinConditions( - Collections.singletonList( - new JoinConditionBuilder() - .joinType(JoinType.INNER_JOIN) - .tableInfo(orderItemTableInfo) - .sourceColumnInfo(orderOrderId) - .targetColumnInfo(orderItemId) - .build() - ) - ) .build(); String query = selectQuery.toString(); logger.debug(query); - assertThat(query).isEqualTo("select * from orders where orders.id = 1 inner join order_items on orders.order_id = order_items.id;"); + assertThat(query).isEqualTo("select * from orders left join order_items on orders.id = order_items.order_id where orders.id = 1;"); } }