diff --git a/src/main/java/constants/CommonConstants.java b/src/main/java/constants/CommonConstants.java index ee6ffc6d..2d74188f 100644 --- a/src/main/java/constants/CommonConstants.java +++ b/src/main/java/constants/CommonConstants.java @@ -9,4 +9,6 @@ private CommonConstants() { public static final String COMMA = ", "; public static final String EQUAL = " = "; public static final String AND = " and "; + public static final String PERIOD = "."; + public static final String UNDER_SCORE = "_"; } diff --git a/src/main/java/docs/lecture.md b/src/main/java/docs/lecture.md new file mode 100644 index 00000000..db0c94d1 --- /dev/null +++ b/src/main/java/docs/lecture.md @@ -0,0 +1,28 @@ +## 3/16 + +### 구현 +```markdown +- Query +- Entity +-> 이렇게 2개의 패키지로 나누고, 1주차 미션과 약간 별개일 수 있다. + +- SessionImpl 에 의해 session 을 계속 넣어서 다닌다. + - `return new StatefulPersistenceContext(this);` +- StatefulPersistenceContext + - `entitiesByKey` + - `collectionsByKey` +- AnnotationBinder 참고 + +- InFlightMetadataCollector + - metadata 수집 + - 캐싱 설정(default = true) + +``` + +### logging +```markdown +- logging.level.org.hibernate.boot=trace +- logging.level.org.hibernate=trace => 직접 api 실행해서 로깅 확인 +- Spring 은 로깅해서 확인해보는 것을 추천 => Spring 공부! +``` + diff --git a/src/main/java/docs/step1.md b/src/main/java/docs/step1.md new file mode 100644 index 00000000..0b020f16 --- /dev/null +++ b/src/main/java/docs/step1.md @@ -0,0 +1,20 @@ +# 1단계 - OneToMany (FetchType.EAGER) + +## 요구사항 1 - Join Query 만들기 + +- Sql 쿼리 문을 수정해 보자 +```java +public class CustomSelect { + +} + +``` + +## 요구사항 2 - Join Query 를 만들어 Entity 화 해보기 + +- FetchType.EAGER 인 경우 +```java +public class SimplePersistenceContext implements PersistenceContext { + +} +``` diff --git a/src/main/java/entity/Order.java b/src/main/java/entity/Order.java new file mode 100644 index 00000000..564fe092 --- /dev/null +++ b/src/main/java/entity/Order.java @@ -0,0 +1,69 @@ +package entity; + +import jakarta.persistence.Column; +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; +import java.util.Objects; + +@Entity +@Table(name = "orders") +public class Order { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "order_number") + private String orderNumber; + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn(name = "order_id") + private List orderItems; + + public Order() { + } + + public Order(Long id, String orderNumber, List orderItems) { + this.id = id; + this.orderNumber = orderNumber; + this.orderItems = orderItems; + } + + public Long getId() { + return id; + } + + public String getOrderNumber() { + return orderNumber; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + Order order = (Order) object; + return Objects.equals(id, order.id) && Objects.equals(orderNumber, order.orderNumber) && Objects.equals(orderItems, order.orderItems); + } + + @Override + public int hashCode() { + return Objects.hash(id, orderNumber, orderItems); + } + + @Override + public String toString() { + return "Order{" + + "id=" + id + + ", orderNumber='" + orderNumber + '\'' + + ", orderItems=" + orderItems + + '}'; + } +} diff --git a/src/main/java/entity/OrderItem.java b/src/main/java/entity/OrderItem.java new file mode 100644 index 00000000..9ead37e8 --- /dev/null +++ b/src/main/java/entity/OrderItem.java @@ -0,0 +1,65 @@ +package entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import java.util.Objects; + +@Entity +@Table(name = "order_items") +public class OrderItem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String product; + + private Integer quantity; + + public OrderItem() { + } + + public OrderItem(Long id, String product, Integer quantity) { + this.id = id; + this.product = product; + this.quantity = quantity; + } + + public Long getId() { + return id; + } + + public String getProduct() { + return product; + } + + public Integer getQuantity() { + return quantity; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + OrderItem orderItem = (OrderItem) object; + return Objects.equals(id, orderItem.id) && Objects.equals(product, orderItem.product) && Objects.equals(quantity, orderItem.quantity); + } + + @Override + public int hashCode() { + return Objects.hash(id, product, quantity); + } + + @Override + public String toString() { + return "OrderItem{" + + "id=" + id + + ", product='" + product + '\'' + + ", quantity=" + quantity + + '}'; + } +} diff --git a/src/main/java/persistence/context/SimplePersistenceContext.java b/src/main/java/persistence/context/SimplePersistenceContext.java index a8da017a..ba6b4176 100644 --- a/src/main/java/persistence/context/SimplePersistenceContext.java +++ b/src/main/java/persistence/context/SimplePersistenceContext.java @@ -1,7 +1,5 @@ package persistence.context; -import dialect.Dialect; -import dialect.H2Dialect; import persistence.entity.EntityCacheKey; import persistence.entity.EntitySnapshot; import pojo.FieldInfos; @@ -13,7 +11,6 @@ public class SimplePersistenceContext implements PersistenceContext { - private final Dialect dialect = new H2Dialect(); private final Map entitiesByKey = new HashMap<>(); private final Map entitySnapshotsByKey = new HashMap<>(); diff --git a/src/main/java/persistence/entity/CustomJpaRepository.java b/src/main/java/persistence/entity/CustomJpaRepository.java index 16408ecf..c63ba430 100644 --- a/src/main/java/persistence/entity/CustomJpaRepository.java +++ b/src/main/java/persistence/entity/CustomJpaRepository.java @@ -28,8 +28,8 @@ private boolean isNew(T entity) { try { Field field = new FieldInfos(entity.getClass().getDeclaredFields()).getIdField(); IdField idField = new IdField(field, entity); - return idField.getFieldInfoTemp().getFieldValue() == null - || isBlankOrEmpty(idField.getFieldInfoTemp().getFieldValue().getValue()); + return idField.getEntityColumn().getFieldValue() == null + || isBlankOrEmpty(idField.getEntityColumn().getFieldValue().getValue()); } catch (IllegalArgumentException e) { return false; } diff --git a/src/main/java/persistence/entity/EntityEntry.java b/src/main/java/persistence/entity/EntityEntry.java index c7a2ecae..68e42dcc 100644 --- a/src/main/java/persistence/entity/EntityEntry.java +++ b/src/main/java/persistence/entity/EntityEntry.java @@ -19,5 +19,6 @@ public interface EntityEntry { void postRemove(); void preReadOnly(); + void postReadOnly(); } diff --git a/src/main/java/persistence/entity/EntityLoader.java b/src/main/java/persistence/entity/EntityLoader.java index 139b1ac6..694d4a0b 100644 --- a/src/main/java/persistence/entity/EntityLoader.java +++ b/src/main/java/persistence/entity/EntityLoader.java @@ -7,4 +7,6 @@ public interface EntityLoader { T findById(Class clazz, Object entity, Object condition); List findAll(Class clazz); + + List findByIdWithAssociation(Class clazz, Object entity, Object condition); } diff --git a/src/main/java/persistence/entity/EntityLoaderImpl.java b/src/main/java/persistence/entity/EntityLoaderImpl.java index 933f35e5..1910fb7c 100644 --- a/src/main/java/persistence/entity/EntityLoaderImpl.java +++ b/src/main/java/persistence/entity/EntityLoaderImpl.java @@ -2,7 +2,9 @@ import jdbc.JdbcTemplate; import jdbc.RowMapperImpl; +import persistence.sql.dml.CustomSelectQueryBuilder; import persistence.sql.dml.SelectQueryBuilder; +import pojo.EntityJoinMetaData; import pojo.EntityMetaData; import java.util.List; @@ -27,4 +29,23 @@ public List findAll(Class clazz) { SelectQueryBuilder selectQueryBuilder = new SelectQueryBuilder(entityMetaData); return jdbcTemplate.query(selectQueryBuilder.findAllQuery(), new RowMapperImpl<>(clazz)); } + + //연관관계가 있는 경우 & eager 타입만 고려 + @Override + public List findByIdWithAssociation(Class clazz, Object entity, Object condition) { + CustomSelectQueryBuilder customSelectQueryBuilder = new CustomSelectQueryBuilder(entityMetaData); + + EntityJoinMetaData entityJoinMetaData = entityMetaData.createEntityJoinMetaDataInfo(); + if (!entityJoinMetaData.isLazy()) { + return eagerTypeQuery(customSelectQueryBuilder, clazz, entity); + } + + //TODO 추후 lazy 타입 넣을 예정 + return jdbcTemplate.query(customSelectQueryBuilder.findByIdJoinQuery(entity, clazz), new RowMapperImpl<>(clazz)); + + } + + private List eagerTypeQuery(CustomSelectQueryBuilder customSelectQueryBuilder, Class clazz, Object entity) { + return jdbcTemplate.query(customSelectQueryBuilder.findByIdJoinQuery(entity, clazz), new RowMapperImpl<>(clazz)); + } } diff --git a/src/main/java/persistence/entity/EntitySnapshot.java b/src/main/java/persistence/entity/EntitySnapshot.java index 66efc8ce..3902840e 100644 --- a/src/main/java/persistence/entity/EntitySnapshot.java +++ b/src/main/java/persistence/entity/EntitySnapshot.java @@ -1,6 +1,6 @@ package persistence.entity; -import pojo.FieldInfo; +import pojo.EntityColumn; import pojo.FieldInfos; import java.lang.reflect.Field; @@ -17,7 +17,7 @@ public class EntitySnapshot { public EntitySnapshot(Object entity) { List fields = new FieldInfos(entity.getClass().getDeclaredFields()).getIdAndColumnFields(); this.map = fields.stream() - .map(field -> new FieldInfo(field, entity)) + .map(field -> new EntityColumn(field, entity)) .collect(Collectors.toMap( fieldInfo -> fieldInfo.getFieldName().getName(), fieldInfo -> fieldInfo.getFieldValue().getValue() diff --git a/src/main/java/persistence/sql/ddl/CreateQueryBuilder.java b/src/main/java/persistence/sql/ddl/CreateQueryBuilder.java index 67178aac..013750d9 100644 --- a/src/main/java/persistence/sql/ddl/CreateQueryBuilder.java +++ b/src/main/java/persistence/sql/ddl/CreateQueryBuilder.java @@ -5,7 +5,6 @@ import jakarta.persistence.Transient; import pojo.ColumnField; import pojo.EntityMetaData; -import pojo.FieldInfos; import pojo.IdField; import java.lang.reflect.Field; @@ -33,10 +32,11 @@ public CreateQueryBuilder(Dialect dialect, EntityMetaData entityMetaData) { } public String createTable(Object entity) { - FieldInfos fieldInfos = new FieldInfos(entity.getClass().getDeclaredFields()); - return String.format(CREATE_TABLE_QUERY, entityMetaData.getTableInfo().getName(), createClause(fieldInfos.getFieldDataList(), entity)); + return String.format(CREATE_TABLE_QUERY, entityMetaData.getEntityName(), + createClause(entityMetaData.getFieldInfos().getFieldDataList(), entity)); } + //create 시에 JoinColumn 어노테이션이 있는 필드는 -> JoinColumn 의 name 을 그 필드 객체 생성 시 만들어줘야 한다. private String createClause(List fields, Object entity) { return fields.stream() .filter(field -> !field.isAnnotationPresent(Transient.class)) diff --git a/src/main/java/persistence/sql/ddl/DropQueryBuilder.java b/src/main/java/persistence/sql/ddl/DropQueryBuilder.java index 26f7b09e..ac2c2e6f 100644 --- a/src/main/java/persistence/sql/ddl/DropQueryBuilder.java +++ b/src/main/java/persistence/sql/ddl/DropQueryBuilder.java @@ -4,7 +4,7 @@ public class DropQueryBuilder { - private static final String DROP_TABLE_QUERY = "DROP TABLE %s IF EXISTS;"; + private static final String DROP_TABLE_QUERY = "DROP TABLE IF EXISTS %s;"; private final EntityMetaData entityMetaData; @@ -13,6 +13,6 @@ public DropQueryBuilder(EntityMetaData entityMetaData) { } public String dropTable() { - return String.format(DROP_TABLE_QUERY, entityMetaData.getTableInfo().getName()); + return String.format(DROP_TABLE_QUERY, entityMetaData.getEntityName()); } } diff --git a/src/main/java/persistence/sql/dml/CustomSelectQueryBuilder.java b/src/main/java/persistence/sql/dml/CustomSelectQueryBuilder.java new file mode 100644 index 00000000..611e31ef --- /dev/null +++ b/src/main/java/persistence/sql/dml/CustomSelectQueryBuilder.java @@ -0,0 +1,56 @@ +package persistence.sql.dml; + +import pojo.EntityJoinMetaData; +import pojo.EntityMetaData; +import pojo.FieldInfos; +import pojo.FieldName; +import pojo.IdField; + +import java.lang.reflect.Field; + +import static constants.CommonConstants.COMMA; +import static constants.CommonConstants.PERIOD; + +public class CustomSelectQueryBuilder { + + private static final String FIND_BY_ID_JOIN_QUERY = "SELECT %s FROM %s LEFT JOIN %s ON %s = %s WHERE %s = %s;"; + + private final EntityMetaData entityMetaData; + private final EntityJoinMetaData entityJoinMetaData; + + public CustomSelectQueryBuilder(EntityMetaData entityMetaData) { + this.entityMetaData = entityMetaData; + this.entityJoinMetaData = entityMetaData.createEntityJoinMetaDataInfo(); + } + + public String findByIdJoinQuery(Object entity, Class clazz) { + Field field = new FieldInfos(clazz.getDeclaredFields()).getIdField(); + IdField idField = new IdField(field, entity); + + String metaDataEntityName = entityMetaData.getEntityName(); + String joinMetaDataEntityName = entityJoinMetaData.getEntityName(); + + return String.format(FIND_BY_ID_JOIN_QUERY, getSelectData(entity), metaDataEntityName, joinMetaDataEntityName, + metaDataEntityName + PERIOD + idField.getFieldNameData(), + joinMetaDataEntityName + PERIOD + entityJoinMetaData.getJoinColumnNameInfo(idField), + metaDataEntityName + PERIOD + idField.getFieldNameData(), idField.getFieldValueData()); + } + + private String getSelectData(Object entity) { + String entityData = entityMetaData.createEntityColumnsInfo(entity) + .stream() + .map(entityColumn -> entityColumn.getFieldName().getName()) + .map(name -> entityMetaData.getEntityName() + PERIOD + name) + .reduce((o1, o2) -> String.join(COMMA, o1, o2)) + .orElseThrow(() -> new IllegalStateException("Id 혹은 Column 타입이 없습니다.")); + + String ownerEntityData = entityJoinMetaData.getFieldNamesInfo() + .stream() + .map(FieldName::getName) + .map(name -> entityJoinMetaData.getEntityName() + PERIOD + name) + .reduce((o1, o2) -> String.join(COMMA, o1, o2)) + .orElseThrow(() -> new IllegalStateException("Id 혹은 Column 타입이 없습니다.")); + + return entityData + COMMA + ownerEntityData; + } +} diff --git a/src/main/java/persistence/sql/dml/DeleteQueryBuilder.java b/src/main/java/persistence/sql/dml/DeleteQueryBuilder.java index f6fde488..c846bd8a 100644 --- a/src/main/java/persistence/sql/dml/DeleteQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/DeleteQueryBuilder.java @@ -17,7 +17,7 @@ public DeleteQueryBuilder(EntityMetaData entityMetaData) { } public String deleteQuery(IdField idField) { - return String.format(DELETE_QUERY, entityMetaData.getTableInfo().getName(), idField.getFieldNameData(), idField.getFieldValueData()); + return String.format(DELETE_QUERY, entityMetaData.getEntityName(), idField.getFieldNameData(), idField.getFieldValueData()); } public String deleteByIdQuery(Object entity) { diff --git a/src/main/java/persistence/sql/dml/SelectQueryBuilder.java b/src/main/java/persistence/sql/dml/SelectQueryBuilder.java index d5fad380..3cb0f60d 100644 --- a/src/main/java/persistence/sql/dml/SelectQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/SelectQueryBuilder.java @@ -18,13 +18,13 @@ public SelectQueryBuilder(EntityMetaData entityMetaData) { } public String findAllQuery() { - return String.format(FIND_ALL_QUERY, entityMetaData.getTableInfo().getName()); + return String.format(FIND_ALL_QUERY, entityMetaData.getEntityName()); } public String findByIdQuery(Object entity, Class clazz, Object condition) { Field field = new FieldInfos(clazz.getDeclaredFields()).getIdField(); IdField idField = new IdField(field, entity); - return String.format(FIND_BY_ID_QUERY, entityMetaData.getTableInfo().getName(), + return String.format(FIND_BY_ID_QUERY, entityMetaData.getEntityName(), idField.getFieldNameData(), condition); } } diff --git a/src/main/java/persistence/sql/dml/UpdateQueryBuilder.java b/src/main/java/persistence/sql/dml/UpdateQueryBuilder.java index d0b5d3f4..a9e03f72 100644 --- a/src/main/java/persistence/sql/dml/UpdateQueryBuilder.java +++ b/src/main/java/persistence/sql/dml/UpdateQueryBuilder.java @@ -1,15 +1,17 @@ package persistence.sql.dml; +import pojo.EntityColumn; import pojo.EntityMetaData; -import pojo.FieldInfo; import pojo.FieldInfos; import java.lang.reflect.Field; import java.util.List; +import java.util.Objects; import static constants.CommonConstants.AND; import static constants.CommonConstants.COMMA; import static constants.CommonConstants.EQUAL; +import static utils.StringUtils.joinNameAndValue; public class UpdateQueryBuilder { @@ -23,16 +25,16 @@ public UpdateQueryBuilder(EntityMetaData entityMetaData) { } public String insertQuery(Object entity) { - return String.format(INSERT_DATA_QUERY, entityMetaData.getTableInfo().getName(), columnsClause(entity), valuesClause(entity)); + return String.format(INSERT_DATA_QUERY, entityMetaData.getEntityName(), columnsClause(entity), valuesClause(entity)); } public String updateQuery(Object entity) { - return String.format(UPDATE_DATA_QUERY, entityMetaData.getTableInfo().getName(), setClause(entity), whereClause(entity)); + return String.format(UPDATE_DATA_QUERY, entityMetaData.getEntityName(), setClause(entity), whereClause(entity)); } private String columnsClause(Object entity) { return new FieldInfos(entity.getClass().getDeclaredFields()).getIdAndColumnFields().stream() - .map(field -> new FieldInfo(field, entity)) + .map(field -> new EntityColumn(field, entity)) .map(fieldInfo -> fieldInfo.getFieldName().getName()) .reduce((o1, o2) -> String.join(COMMA, o1, String.valueOf(o2))) .orElseThrow(() -> new IllegalStateException("Id 혹은 Column 타입이 없습니다.")); @@ -40,7 +42,7 @@ private String columnsClause(Object entity) { private String valuesClause(Object entity) { return new FieldInfos(entity.getClass().getDeclaredFields()).getIdAndColumnFields().stream() - .map(field -> new FieldInfo(field, entity)) + .map(field -> new EntityColumn(field, entity)) .map(fieldInfo -> fieldInfo.getFieldValue().getValue()) .reduce((o1, o2) -> String.join(COMMA, o1, String.valueOf(o2))) .orElseThrow(() -> new IllegalStateException("Id 혹은 Column 타입이 없습니다.")); @@ -58,10 +60,11 @@ private String whereClause(Object entity) { private String fieldNameAndValueClause(Object entity, List fields, String delimiter) { return fields.stream() - .map(field -> new FieldInfo(field, entity)) - .filter(FieldInfo::isNotBlankOrEmpty) - .map(fieldInfo -> fieldInfo.joinNameAndValueWithDelimiter(EQUAL)) - .reduce((o1, o2) -> String.join(delimiter, o1, String.valueOf(o2))) + .map(field -> new EntityColumn(field, entity)) + .filter(fieldInfo -> Objects.nonNull(fieldInfo.getFieldName()) && Objects.nonNull(fieldInfo.getFieldValue())) + .map(fieldInfo -> + joinNameAndValue(EQUAL, fieldInfo.getFieldName().getName(), String.valueOf(fieldInfo.getFieldValue().getValue()))) + .reduce((o1, o2) -> String.join(delimiter, o1, o2)) .orElseThrow(() -> new IllegalStateException("update 데이터가 없습니다.")); } } diff --git a/src/main/java/pojo/ColumnField.java b/src/main/java/pojo/ColumnField.java index 6ac8c35b..c64a5add 100644 --- a/src/main/java/pojo/ColumnField.java +++ b/src/main/java/pojo/ColumnField.java @@ -7,10 +7,10 @@ public class ColumnField implements FieldData { - private final FieldInfo fieldInfo; + private final EntityColumn entityColumn; public ColumnField(Field field, Object entity) { - this.fieldInfo = new FieldInfo(field, entity); + this.entityColumn = new EntityColumn(field, entity); } public String getFieldLength(boolean isVarcharType) { @@ -18,7 +18,7 @@ public String getFieldLength(boolean isVarcharType) { } public String getColumnNullConstraint() { - if (!isColumnField() || fieldInfo.getField().getAnnotation(Column.class).nullable()) { + if (!isColumnField() || entityColumn.getField().getAnnotation(Column.class).nullable()) { return Constraints.NULL.getName(); } return Constraints.NOT_NULL.getName(); @@ -26,7 +26,7 @@ public String getColumnNullConstraint() { private String getColumnLength(boolean isVarcharType) { if (isColumnField() && isVarcharType) { - return String.valueOf(fieldInfo.getField().getAnnotation(Column.class).length()); + return String.valueOf(entityColumn.getField().getAnnotation(Column.class).length()); } if (isColumnField() && !isVarcharType) { @@ -37,8 +37,8 @@ private String getColumnLength(boolean isVarcharType) { } private String getLengthOrDefaultValue(int defaultLengthValue) { - return fieldInfo.getField().getAnnotation(Column.class).length() == defaultLengthValue ? null - : String.valueOf(fieldInfo.getField().getAnnotation(Column.class).length()); + return entityColumn.getField().getAnnotation(Column.class).length() == defaultLengthValue ? null + : String.valueOf(entityColumn.getField().getAnnotation(Column.class).length()); } @Override @@ -58,16 +58,16 @@ public boolean isNotTransientField() { @Override public boolean isNullableField() { - return fieldInfo.getField().getAnnotation(Column.class).nullable(); + return entityColumn.getField().getAnnotation(Column.class).nullable(); } @Override public String getFieldNameData() { - return fieldInfo.getFieldName().getName(); + return entityColumn.getFieldName().getName(); } @Override public Object getFieldValueData() { - return fieldInfo.getFieldValue().getValue(); + return entityColumn.getFieldValue().getValue(); } } diff --git a/src/main/java/pojo/FieldInfo.java b/src/main/java/pojo/EntityColumn.java similarity index 62% rename from src/main/java/pojo/FieldInfo.java rename to src/main/java/pojo/EntityColumn.java index c0acbb25..61ecfb43 100644 --- a/src/main/java/pojo/FieldInfo.java +++ b/src/main/java/pojo/EntityColumn.java @@ -1,15 +1,18 @@ package pojo; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; + import java.lang.reflect.Field; import java.util.Objects; -public class FieldInfo { +public class EntityColumn { private final Field field; private final FieldName fieldName; private final FieldValue fieldValue; - public FieldInfo(Field field, Object entity) { + public EntityColumn(Field field, Object entity) { if (Objects.isNull(field)) { throw new IllegalArgumentException("field 가 null 이어서는 안됩니다."); } @@ -30,11 +33,10 @@ public FieldValue getFieldValue() { return fieldValue; } - public boolean isNotBlankOrEmpty() { - return Objects.nonNull(fieldName) && Objects.nonNull(fieldValue); - } - - public String joinNameAndValueWithDelimiter(String delimiter) { - return String.join(delimiter, fieldName.getName(), String.valueOf(fieldValue.getValue())); + public H2GenerationType getGenerationType() { + if (field.isAnnotationPresent(GeneratedValue.class)) { + return H2GenerationType.from(field.getAnnotation(GeneratedValue.class).strategy()); + } + return null; } } diff --git a/src/main/java/pojo/EntityJoinMetaData.java b/src/main/java/pojo/EntityJoinMetaData.java new file mode 100644 index 00000000..78d2aeec --- /dev/null +++ b/src/main/java/pojo/EntityJoinMetaData.java @@ -0,0 +1,69 @@ +package pojo; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +import static constants.CommonConstants.UNDER_SCORE; +import static utils.StringUtils.isBlankOrEmpty; + +public class EntityJoinMetaData { + + private final Class clazz; + private final String entityName; + private final FieldInfos fieldInfos; + private final Field joinField; + private final boolean lazy; + + public EntityJoinMetaData(Class clazz, Field field) { + if (!clazz.isAnnotationPresent(Entity.class)) { + throw new IllegalStateException("Entity 클래스가 아닙니다."); + } + this.clazz = clazz; + this.entityName = getEntityNameInfo(); + this.fieldInfos = new FieldInfos(clazz.getDeclaredFields()); + this.joinField = field; + this.lazy = isLazy(field); + } + + public String getEntityName() { + return entityName; + } + + public boolean isLazy() { + return lazy; + } + + private String getEntityNameInfo() { + if (clazz.isAnnotationPresent(Table.class) && !isBlankOrEmpty(clazz.getAnnotation(Table.class).name())) { + return clazz.getAnnotation(Table.class).name(); + } + + return clazz.getSimpleName().toLowerCase(); + } + + private boolean isLazy(Field field) { + //일단 OneToMany 만 고려 + return !field.getAnnotation(OneToMany.class).fetch().equals(FetchType.EAGER); + } + + public String getJoinColumnNameInfo(IdField entityMetaDataIdField) { + if (joinField.isAnnotationPresent(JoinColumn.class) && !isBlankOrEmpty(joinField.getAnnotation(JoinColumn.class).name())) { + return joinField.getAnnotation(JoinColumn.class).name(); + } + + return getEntityNameInfo() + UNDER_SCORE + entityMetaDataIdField.getFieldNameData(); + } + + public List getFieldNamesInfo() { + return fieldInfos.getIdAndColumnFields().stream() + .map(FieldName::new) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/pojo/EntityMetaData.java b/src/main/java/pojo/EntityMetaData.java index 86f64951..f6a9a843 100644 --- a/src/main/java/pojo/EntityMetaData.java +++ b/src/main/java/pojo/EntityMetaData.java @@ -1,19 +1,61 @@ package pojo; import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static utils.StringUtils.isBlankOrEmpty; + +//Order public class EntityMetaData { - private final TableInfo tableInfo; + private final Class clazz; + private final String entityName; + private final FieldInfos fieldInfos; public EntityMetaData(Class clazz) { if (!clazz.isAnnotationPresent(Entity.class)) { throw new IllegalStateException("Entity 클래스가 아닙니다."); } - this.tableInfo = new TableInfo(clazz); + this.clazz = clazz; + this.entityName = getEntityNameInfo(); + this.fieldInfos = new FieldInfos(clazz.getDeclaredFields()); + } + + public String getEntityName() { + return entityName; + } + + public FieldInfos getFieldInfos() { + return fieldInfos; + } + + private String getEntityNameInfo() { + if (clazz.isAnnotationPresent(Table.class) && !isBlankOrEmpty(clazz.getAnnotation(Table.class).name())) { + return clazz.getAnnotation(Table.class).name(); + } + + return clazz.getSimpleName().toLowerCase(); } - public TableInfo getTableInfo() { - return tableInfo; + public List createEntityColumnsInfo(Object entity) { + return fieldInfos.getIdAndColumnFields().stream() + .map(field -> new EntityColumn(field, entity)) + .collect(Collectors.toList()); + } + + public EntityJoinMetaData createEntityJoinMetaDataInfo() { + Optional joinColumnField = fieldInfos.getJoinColumnField(); + if (joinColumnField.isEmpty()) { + return null; + } + + Class joinClass = (Class) ((ParameterizedType) joinColumnField.get().getGenericType()).getActualTypeArguments()[0]; + return new EntityJoinMetaData(joinClass, joinColumnField.get()); } } diff --git a/src/main/java/pojo/FieldInfos.java b/src/main/java/pojo/FieldInfos.java index 37dc97f7..ab45b529 100644 --- a/src/main/java/pojo/FieldInfos.java +++ b/src/main/java/pojo/FieldInfos.java @@ -1,6 +1,7 @@ package pojo; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Transient; import java.lang.reflect.Field; @@ -8,39 +9,58 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; public class FieldInfos { - private final List fieldInfoList; + private final List fieldList; public FieldInfos(Field[] fields) { if (Objects.isNull(fields)) { throw new IllegalArgumentException("fields 가 null 이어서는 안됩니다."); } - this.fieldInfoList = Arrays.stream(fields).collect(Collectors.toList()); + this.fieldList = Arrays.stream(fields).filter(field -> !isTransientField(field)).collect(Collectors.toList()); } public List getFieldDataList() { - return fieldInfoList; + return fieldList; } public Field getIdField() { - return fieldInfoList.stream() - .filter(field -> field.isAnnotationPresent(Id.class)) + return fieldList.stream() + .filter(this::isIdField) .findFirst() .orElseThrow(() -> new IllegalStateException("Id 필드가 존재하지 않습니다.")); } public List getColumnFields() { - return fieldInfoList.stream() - .filter(field -> !field.isAnnotationPresent(Transient.class) && !field.isAnnotationPresent(Id.class)) + return fieldList.stream() + .filter(field -> !isIdField(field) && !isJoinColumnField(field)) .collect(Collectors.toCollection(LinkedList::new)); } public List getIdAndColumnFields() { - return fieldInfoList.stream() - .filter(field -> !field.isAnnotationPresent(Transient.class)) + return fieldList.stream() + .filter(field -> !isJoinColumnField(field)) .collect(Collectors.toCollection(LinkedList::new)); } + + public Optional getJoinColumnField() { + return fieldList.stream() + .filter(this::isJoinColumnField) + .findFirst(); + } + + private boolean isIdField(Field field) { + return field.isAnnotationPresent(Id.class); + } + + private boolean isTransientField(Field field) { + return field.isAnnotationPresent(Transient.class); + } + + private boolean isJoinColumnField(Field field) { + return field.isAnnotationPresent(JoinColumn.class); + } } diff --git a/src/main/java/pojo/FieldNameAndValue.java b/src/main/java/pojo/FieldNameAndValue.java deleted file mode 100644 index 4e192bf4..00000000 --- a/src/main/java/pojo/FieldNameAndValue.java +++ /dev/null @@ -1,30 +0,0 @@ -package pojo; - -import java.util.Objects; - -public class FieldNameAndValue { - - private final FieldName fieldName; - private final FieldValue fieldValue; - - public FieldNameAndValue(FieldName fieldName, FieldValue fieldValue) { - this.fieldName = fieldName; - this.fieldValue = fieldValue; - } - - public FieldName getFieldName() { - return fieldName; - } - - public FieldValue getFieldValue() { - return fieldValue; - } - - public String joinNameAndValueWithDelimiter(String delimiter) { - return String.join(delimiter, getFieldName().getName(), String.valueOf(getFieldValue().getValue())); - } - - public boolean isNotBlankOrEmpty() { - return Objects.nonNull(fieldName) && Objects.nonNull(fieldValue); - } -} diff --git a/src/main/java/pojo/IdField.java b/src/main/java/pojo/IdField.java index bd3b746d..f066c9f6 100644 --- a/src/main/java/pojo/IdField.java +++ b/src/main/java/pojo/IdField.java @@ -1,22 +1,20 @@ package pojo; -import jakarta.persistence.GeneratedValue; - import java.lang.reflect.Field; import java.util.Objects; public class IdField implements FieldData { - private final FieldInfo fieldInfo; + private final EntityColumn entityColumn; private final H2GenerationType generationType; public IdField(Field field, Object entity) { - this.fieldInfo = new FieldInfo(field, entity); + this.entityColumn = new EntityColumn(field, entity); this.generationType = getGenerationType(); } - public FieldInfo getFieldInfoTemp() { - return fieldInfo; + public EntityColumn getEntityColumn() { + return entityColumn; } public String getGenerationTypeStrategy() { @@ -29,10 +27,7 @@ public boolean isGenerationTypeAutoOrIdentity() { } private H2GenerationType getGenerationType() { - if (fieldInfo.getField().isAnnotationPresent(GeneratedValue.class)) { - return H2GenerationType.from(fieldInfo.getField().getAnnotation(GeneratedValue.class).strategy()); - } - return null; + return entityColumn.getGenerationType(); } @Override @@ -57,11 +52,11 @@ public boolean isNullableField() { @Override public String getFieldNameData() { - return fieldInfo.getFieldName().getName(); + return entityColumn.getFieldName().getName(); } @Override public Object getFieldValueData() { - return fieldInfo.getFieldValue().getValue(); + return entityColumn.getFieldValue().getValue(); } } diff --git a/src/main/java/utils/StringUtils.java b/src/main/java/utils/StringUtils.java index 2e6e7c34..f715ad48 100644 --- a/src/main/java/utils/StringUtils.java +++ b/src/main/java/utils/StringUtils.java @@ -9,4 +9,8 @@ private StringUtils() { public static boolean isBlankOrEmpty(String target) { return target == null || target.isBlank() || target.isEmpty(); } + + public static String joinNameAndValue(String delimiter, String name, String value) { + return String.join(delimiter, name, value); + } } diff --git a/src/test/java/persistence/JpaTest.java b/src/test/java/persistence/JpaTest.java new file mode 100644 index 00000000..91d36821 --- /dev/null +++ b/src/test/java/persistence/JpaTest.java @@ -0,0 +1,83 @@ +package persistence; + +import database.DatabaseServer; +import dialect.Dialect; +import dialect.H2Dialect; +import entity.Order; +import entity.OrderItem; +import entity.Person3; +import jdbc.JdbcTemplate; +import persistence.context.PersistenceContext; +import persistence.context.SimplePersistenceContext; +import persistence.entity.CustomJpaRepository; +import persistence.entity.EntityLoader; +import persistence.entity.EntityLoaderImpl; +import persistence.entity.EntityPersister; +import persistence.entity.EntityPersisterImpl; +import persistence.entity.JpaRepository; +import persistence.entity.SimpleEntityEntry; +import persistence.entity.SimpleEntityManager; +import pojo.EntityMetaData; +import pojo.EntityStatus; + +import java.util.List; + +public abstract class JpaTest { + + protected static Dialect dialect = new H2Dialect(); + protected static DatabaseServer server; + protected static JdbcTemplate jdbcTemplate; + protected static EntityPersister entityPersister; + protected static EntityLoader entityLoader; + protected static SimpleEntityManager simpleEntityManager; + protected static PersistenceContext persistenceContext; + protected static SimpleEntityEntry entityEntry; + protected static JpaRepository jpaRepository; + + protected static Person3 person = new Person3(1L, "test", 20, "test@test.com"); + protected static OrderItem orderItem1 = new OrderItem(1L, "A", 1); + protected static OrderItem orderItem2 = new OrderItem(2L, "B", 10); + protected static OrderItem orderItem3 = new OrderItem(3L, "C", 5); + protected static Order order = new Order(1L, "test1", List.of(orderItem1, orderItem2, orderItem3)); + + protected static void initForTest(EntityMetaData entityMetaData) { + entityPersister = new EntityPersisterImpl(jdbcTemplate, entityMetaData); + entityLoader = new EntityLoaderImpl(jdbcTemplate, entityMetaData); + entityEntry = new SimpleEntityEntry(EntityStatus.LOADING); + + persistenceContext = new SimplePersistenceContext(); + simpleEntityManager = new SimpleEntityManager(entityPersister, entityLoader, persistenceContext, entityEntry); + jpaRepository = new CustomJpaRepository(simpleEntityManager); + } + + protected static void createOrderAndOrderItemTable() { + String createOrderSql = "create table orders(id bigint auto_increment primary key, order_number varchar(255) null);"; + String createOrderItemSql = "create table order_items(id bigint auto_increment primary key, product varchar(255) null, quantity int null, order_id bigint null, foreign key (order_id) references orders (id));"; + + jdbcTemplate.execute(createOrderSql); + jdbcTemplate.execute(createOrderItemSql); + } + + protected static void insertOrderAndOrderItemData() { + String insertOrderSql = "insert into orders (id, order_number) values (" + order.getId() + ", '" + order.getOrderNumber() + "');"; + String insertOrderItemSql1 = "insert into order_items (id, product, quantity, order_id) " + + "values (" + orderItem1.getId() + ", '" + orderItem1.getProduct() + "' , " + orderItem1.getQuantity() + ", " + order.getId() + ");"; + String insertOrderItemSql2 = "insert into order_items (id, product, quantity, order_id) " + + "values (" + orderItem2.getId() + ", '" + orderItem2.getProduct() + "' , " + orderItem2.getQuantity() + ", " + order.getId() + ");"; + String insertOrderItemSql3 = "insert into order_items (id, product, quantity, order_id) " + + "values (" + orderItem3.getId() + ", '" + orderItem3.getProduct() + "' , " + orderItem3.getQuantity() + ", " + order.getId() + ");"; + + jdbcTemplate.execute(insertOrderSql); + jdbcTemplate.execute(insertOrderItemSql1); + jdbcTemplate.execute(insertOrderItemSql2); + jdbcTemplate.execute(insertOrderItemSql3); + } + + protected static void dropOrderAndOrderItemTable() { + String dropOrderItemTable = "drop table order_items;"; + String dropOrderTable = "drop table orders;"; + + jdbcTemplate.execute(dropOrderItemTable); + jdbcTemplate.execute(dropOrderTable); + } +} diff --git a/src/test/java/persistence/context/SimplePersistenceContextTest.java b/src/test/java/persistence/context/SimplePersistenceContextTest.java index 8527746f..4cee5f2a 100644 --- a/src/test/java/persistence/context/SimplePersistenceContextTest.java +++ b/src/test/java/persistence/context/SimplePersistenceContextTest.java @@ -1,9 +1,6 @@ package persistence.context; -import database.DatabaseServer; import database.H2; -import dialect.Dialect; -import dialect.H2Dialect; import entity.Person3; import jdbc.JdbcTemplate; import org.junit.jupiter.api.AfterAll; @@ -12,13 +9,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import persistence.entity.EntityLoader; -import persistence.entity.EntityLoaderImpl; -import persistence.entity.EntityPersister; -import persistence.entity.EntityPersisterImpl; +import persistence.JpaTest; import persistence.entity.EntitySnapshot; -import persistence.entity.SimpleEntityEntry; -import persistence.entity.SimpleEntityManager; import persistence.sql.ddl.CreateQueryBuilder; import persistence.sql.ddl.DropQueryBuilder; import pojo.EntityMetaData; @@ -33,37 +25,21 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; -class SimplePersistenceContextTest { +class SimplePersistenceContextTest extends JpaTest { - static Dialect dialect = new H2Dialect(); - static EntityMetaData entityMetaData = new EntityMetaData(Person3.class); - - static DatabaseServer server; - static JdbcTemplate jdbcTemplate; - static EntityPersister entityPersister; - static EntityLoader entityLoader; - static SimpleEntityManager simpleEntityManager; - static PersistenceContext persistenceContext; - static SimpleEntityEntry entityEntry; - - Person3 person; + static EntityMetaData entityMetaData; @BeforeAll static void init() throws SQLException { server = new H2(); server.start(); - jdbcTemplate = new JdbcTemplate(server.getConnection()); - entityPersister = new EntityPersisterImpl(jdbcTemplate, entityMetaData); - entityLoader = new EntityLoaderImpl(jdbcTemplate, entityMetaData); - persistenceContext = new SimplePersistenceContext(); - entityEntry = new SimpleEntityEntry(EntityStatus.LOADING); - simpleEntityManager = new SimpleEntityManager(entityPersister, entityLoader, persistenceContext, entityEntry); } @BeforeEach void setUp() { - person = new Person3(1L, "test", 20, "test@test.com"); + entityMetaData = new EntityMetaData(Person3.class); + initForTest(entityMetaData); createTable(); } diff --git a/src/test/java/persistence/entity/CustomJpaRepositoryTest.java b/src/test/java/persistence/entity/CustomJpaRepositoryTest.java index b251b3dd..225184ce 100644 --- a/src/test/java/persistence/entity/CustomJpaRepositoryTest.java +++ b/src/test/java/persistence/entity/CustomJpaRepositoryTest.java @@ -1,9 +1,6 @@ package persistence.entity; -import database.DatabaseServer; import database.H2; -import dialect.Dialect; -import dialect.H2Dialect; import entity.Person3; import jdbc.JdbcTemplate; import org.junit.jupiter.api.AfterAll; @@ -12,51 +9,31 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import persistence.context.PersistenceContext; -import persistence.context.SimplePersistenceContext; +import persistence.JpaTest; import persistence.sql.ddl.CreateQueryBuilder; import persistence.sql.ddl.DropQueryBuilder; import pojo.EntityMetaData; -import pojo.EntityStatus; import java.sql.SQLException; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; -class CustomJpaRepositoryTest { +class CustomJpaRepositoryTest extends JpaTest { - static Dialect dialect = new H2Dialect(); - static EntityMetaData entityMetaData = new EntityMetaData(Person3.class); - - static DatabaseServer server; - static JdbcTemplate jdbcTemplate; - static EntityPersister entityPersister; - static EntityLoader entityLoader; - static SimpleEntityManager simpleEntityManager; - static PersistenceContext persistenceContext; - static JpaRepository jpaRepository; - static EntityEntry entityEntry; - - Person3 person; + static EntityMetaData entityMetaData; @BeforeAll static void init() throws SQLException { server = new H2(); server.start(); - jdbcTemplate = new JdbcTemplate(server.getConnection()); - entityPersister = new EntityPersisterImpl(jdbcTemplate, entityMetaData); - entityLoader = new EntityLoaderImpl(jdbcTemplate, entityMetaData); - persistenceContext = new SimplePersistenceContext(); - entityEntry = new SimpleEntityEntry(EntityStatus.LOADING); - simpleEntityManager = new SimpleEntityManager(entityPersister, entityLoader, persistenceContext, entityEntry); - jpaRepository = new CustomJpaRepository(simpleEntityManager); } @BeforeEach void setUp() { - person = new Person3(1L, "test", 20, "test@test.com"); + entityMetaData = new EntityMetaData(Person3.class); + initForTest(entityMetaData); createTable(); } diff --git a/src/test/java/persistence/entity/EntityLoaderImplTest.java b/src/test/java/persistence/entity/EntityLoaderImplTest.java index 17ab4dce..f539b89c 100644 --- a/src/test/java/persistence/entity/EntityLoaderImplTest.java +++ b/src/test/java/persistence/entity/EntityLoaderImplTest.java @@ -1,9 +1,7 @@ package persistence.entity; -import database.DatabaseServer; import database.H2; -import dialect.Dialect; -import dialect.H2Dialect; +import entity.Order; import entity.Person3; import jdbc.JdbcTemplate; import org.junit.jupiter.api.AfterAll; @@ -12,49 +10,32 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import persistence.context.PersistenceContext; -import persistence.context.SimplePersistenceContext; +import persistence.JpaTest; import persistence.sql.ddl.CreateQueryBuilder; import persistence.sql.ddl.DropQueryBuilder; import pojo.EntityMetaData; -import pojo.EntityStatus; import java.sql.SQLException; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -class EntityLoaderImplTest { +class EntityLoaderImplTest extends JpaTest { - static Dialect dialect = new H2Dialect(); - static EntityMetaData entityMetaData = new EntityMetaData(Person3.class); - - static DatabaseServer server; - static JdbcTemplate jdbcTemplate; - static EntityPersister entityPersister; - static EntityLoader entityLoader; - static SimpleEntityManager simpleEntityManager; - static PersistenceContext persistenceContext; - static EntityEntry entityEntry; - - Person3 person; + static EntityMetaData entityMetaData; @BeforeAll static void init() throws SQLException { server = new H2(); server.start(); - jdbcTemplate = new JdbcTemplate(server.getConnection()); - entityPersister = new EntityPersisterImpl(jdbcTemplate, entityMetaData); - entityLoader = new EntityLoaderImpl(jdbcTemplate, entityMetaData); - persistenceContext = new SimplePersistenceContext(); - entityEntry = new SimpleEntityEntry(EntityStatus.LOADING); - simpleEntityManager = new SimpleEntityManager(entityPersister, entityLoader, persistenceContext, entityEntry); } @BeforeEach void setUp() { - person = new Person3(1L, "test", 20, "test@test.com"); + entityMetaData = new EntityMetaData(Person3.class); + initForTest(entityMetaData); createTable(); } @@ -68,7 +49,7 @@ static void destroy() { server.stop(); } - @DisplayName("findById 테스트") + @DisplayName("findById 테스트 - 연관관계가 없는 경우") @Test void findByIdTest() { entityPersister.insert(person); @@ -81,6 +62,21 @@ void findByIdTest() { ); } + @DisplayName("findById 테스트 - 연관관계가 있는 경우") + @Test + void findByIdWithAssociationTest() { + entityMetaData = new EntityMetaData(Order.class); + initForTest(entityMetaData); + + createOrderAndOrderItemTable(); + insertOrderAndOrderItemData(); + + List savedOrderList = entityLoader.findByIdWithAssociation(order.getClass(), order, order.getId()); + assertThat(savedOrderList).hasSize(3); + + dropOrderAndOrderItemTable(); + } + @DisplayName("findAll 테스트") @Test void findAllTest() { @@ -96,6 +92,7 @@ void findAllTest() { } private void createTable() { + dropTable(); CreateQueryBuilder createQueryBuilder = new CreateQueryBuilder(dialect, entityMetaData); jdbcTemplate.execute(createQueryBuilder.createTable(person)); } diff --git a/src/test/java/persistence/entity/EntityPersisterImplTest.java b/src/test/java/persistence/entity/EntityPersisterImplTest.java index a0d4a3aa..30d4321a 100644 --- a/src/test/java/persistence/entity/EntityPersisterImplTest.java +++ b/src/test/java/persistence/entity/EntityPersisterImplTest.java @@ -1,9 +1,6 @@ package persistence.entity; -import database.DatabaseServer; import database.H2; -import dialect.Dialect; -import dialect.H2Dialect; import entity.Person3; import jdbc.JdbcTemplate; import org.junit.jupiter.api.AfterAll; @@ -12,13 +9,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import persistence.context.PersistenceContext; -import persistence.context.SimplePersistenceContext; +import persistence.JpaTest; import persistence.sql.ddl.CreateQueryBuilder; import persistence.sql.ddl.DropQueryBuilder; import persistence.sql.dml.UpdateQueryBuilder; import pojo.EntityMetaData; -import pojo.EntityStatus; import java.sql.SQLException; @@ -26,37 +21,21 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; -class EntityPersisterImplTest { +class EntityPersisterImplTest extends JpaTest { - static Dialect dialect = new H2Dialect(); - static EntityMetaData entityMetaData = new EntityMetaData(Person3.class); - - static DatabaseServer server; - static JdbcTemplate jdbcTemplate; - static EntityPersister entityPersister; - static EntityLoader entityLoader; - static SimpleEntityManager simpleEntityManager; - static PersistenceContext persistenceContext; - static EntityEntry entityEntry; - - Person3 person; + static EntityMetaData entityMetaData; @BeforeAll static void init() throws SQLException { server = new H2(); server.start(); - jdbcTemplate = new JdbcTemplate(server.getConnection()); - entityPersister = new EntityPersisterImpl(jdbcTemplate, entityMetaData); - entityLoader = new EntityLoaderImpl(jdbcTemplate, entityMetaData); - persistenceContext = new SimplePersistenceContext(); - entityEntry = new SimpleEntityEntry(EntityStatus.LOADING); - simpleEntityManager = new SimpleEntityManager(entityPersister, entityLoader, persistenceContext, entityEntry); } @BeforeEach void setUp() { - person = new Person3(1L, "test", 20, "test@test.com"); + entityMetaData = new EntityMetaData(Person3.class); + initForTest(entityMetaData); createTable(); } @@ -75,6 +54,7 @@ static void destroy() { void insertTest() { entityPersister.insert(person); Person3 person3 = simpleEntityManager.find(person, person.getClass(), person.getId()); + assertAll( () -> assertThat(person3.getId()).isEqualTo(person.getId()), () -> assertThat(person3.getName()).isEqualTo(person.getName()), diff --git a/src/test/java/persistence/sql/CustomSelectQueryBuilderTest.java b/src/test/java/persistence/sql/CustomSelectQueryBuilderTest.java new file mode 100644 index 00000000..858f786d --- /dev/null +++ b/src/test/java/persistence/sql/CustomSelectQueryBuilderTest.java @@ -0,0 +1,60 @@ +package persistence.sql; + +import database.H2; +import entity.Order; +import jdbc.JdbcTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import persistence.JpaTest; +import persistence.sql.dml.CustomSelectQueryBuilder; +import pojo.EntityMetaData; + +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +class CustomSelectQueryBuilderTest extends JpaTest { + + static EntityMetaData entityMetaData; + + @BeforeAll + static void init() throws SQLException { + server = new H2(); + server.start(); + jdbcTemplate = new JdbcTemplate(server.getConnection()); + } + + @BeforeEach + void setUp() { + entityMetaData = new EntityMetaData(Order.class); + initForTest(entityMetaData); + createOrderAndOrderItemTable(); + insertOrderAndOrderItemData(); + } + + @AfterEach + void remove() { + dropOrderAndOrderItemTable(); + } + + @AfterAll + static void destroy() { + server.stop(); + } + + @DisplayName("OneToMany 를 갖고 있는 Entity 클래스의 select 쿼리는 join 문 포함 필요") + @Test + void selectSqlWithJoinColumn() { + entityMetaData = new EntityMetaData(Order.class); + + CustomSelectQueryBuilder customSelectQueryBuilder = new CustomSelectQueryBuilder(entityMetaData); + String selectJoinQuery = customSelectQueryBuilder.findByIdJoinQuery(order, Order.class); + + String resultQuery = "SELECT orders.id, orders.order_number, order_items.id, order_items.product, order_items.quantity FROM orders LEFT JOIN order_items ON orders.id = order_items.order_id WHERE orders.id = 1;"; + assertThat(selectJoinQuery).isEqualTo(resultQuery); + } +} diff --git a/src/test/java/pojo/EntityMetaDataTest.java b/src/test/java/pojo/EntityMetaDataTest.java new file mode 100644 index 00000000..9a4e653c --- /dev/null +++ b/src/test/java/pojo/EntityMetaDataTest.java @@ -0,0 +1,25 @@ +package pojo; + +import entity.Person2; +import entity.Person3; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class EntityMetaDataTest { + + @DisplayName("Table 어노테이션이 없거나 있어도 name 필드가 없을 경우 class 의 simpleName 을 반환한다.") + @Test + void entityNameInfo_ShouldReturnSimpleName() { + EntityMetaData entityMetaData = new EntityMetaData(Person2.class); + assertThat(entityMetaData.getEntityName()).isEqualTo("person2"); + } + + @DisplayName("Table 어노테이션이 있고 name 필드가 있을 경우 name 을 반환한다.") + @Test + void entityNameInfo_ShouldReturnTableName() { + EntityMetaData entityMetaData = new EntityMetaData(Person3.class); + assertThat(entityMetaData.getEntityName()).isEqualTo("users"); + } +}