Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Association - Step 1단계] OneToMany (FetchType.EAGER) #86

Open
wants to merge 30 commits into
base: parkje0927
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
856e473
feat: step1 - 기존 EntityMetaData 수정 및 EntityJoinMetaData 클래스 추가
parkje0927 Mar 19, 2024
f54f250
refactor: step1 - 기존 클래스 네이밍 변경 및 EntityMetaData 리팩토링
parkje0927 Mar 19, 2024
a27127b
refactor: step1 - 불필요한 클래스 삭제 및 변경
parkje0927 Mar 19, 2024
588fac7
feat: step1 - 엔티티 추가
parkje0927 Mar 19, 2024
63e5646
docs: step1 - 강의 내용 및 요구사항 문서 정리
parkje0927 Mar 19, 2024
3cc9e12
test: step1 - 기존 테스트 코드 개선, 중복 코드 추상 클래스로 정리
parkje0927 Mar 19, 2024
4600e06
test: step1 - join select 문 테스트 코드 추가
parkje0927 Mar 19, 2024
0047ab4
chore: step1 - 기존 메소드 유틸 클래스로 이관
parkje0927 Mar 19, 2024
82c0547
feat: step1 - CustomSelectQueryBuilder 추가
parkje0927 Mar 19, 2024
69ab69f
feat: step1 - EntityMetaData, EntityJoinMetaData 의 인터페이스 생성
parkje0927 Mar 19, 2024
75a7a28
refactor: step1 - 불필요한 클래스 삭제
parkje0927 Mar 20, 2024
de82331
chore: step1 - 주석 정리
parkje0927 Mar 20, 2024
a87eb1e
refactor: step1 - 불필요한 클래스 삭제
parkje0927 Mar 21, 2024
b423263
refactor: step1 - 메소드명 변경 및 객체에 책임을 위임하는 방법으로 변경
parkje0927 Mar 21, 2024
0a3c3a1
refactor: step1 - GenerationType 확인 메소드 추가
parkje0927 Mar 21, 2024
4314890
refactor: step1 - 리뷰 내용을 참고하여 EntityMetaData, EntityJoinMetaData 역할을 재정의
parkje0927 Mar 21, 2024
32f34f2
refactor: step1 - isTransient 필드 체크 로직을 초기화 시 처리하도록 수정
parkje0927 Mar 21, 2024
0e8b45e
refactor: step1 - EntityMetaData 초기화 시 EntityJoinMetaData 도 초기화 및 생성을…
parkje0927 Mar 21, 2024
ab58ef3
test: step1 - 테스트 추상화 범위 및 객체 초기화 방법 변경
parkje0927 Mar 21, 2024
a129006
chore: step1 - inline 정리
parkje0927 Mar 21, 2024
9f25689
refactor: step1 - 공통 상수 추가
parkje0927 Mar 25, 2024
6b169ca
refactor: step1 - 피드백 반영하여 수정
parkje0927 Mar 25, 2024
f29d118
feat: step1 - 요구사항 2번을 위해 loader 메소드 추가
parkje0927 Mar 25, 2024
3334c99
test: step1 - 테스트 static 변수 추가 및 join 엔티티화 테스트 코드 추가
parkje0927 Mar 25, 2024
2939e5b
refactor: step1 - 삼항연산자 if 문으로 수정
parkje0927 Apr 1, 2024
d61fa70
test: step1 - EntityMetaData 내 entityName 관련 테스트 추가
parkje0927 Apr 1, 2024
efb21b1
refactor: step1 - EntityMetaData 에서는 class 정보만 가지고 객체를 생성하도록 수정
parkje0927 Apr 8, 2024
0f6ac2a
test: step1 - 테스트 수정 및 기존 연관관계가 있는 findById 테스트 loader 테스트로 이관
parkje0927 Apr 8, 2024
19431c0
refactor: step1 - EntityJoinMetaData 에서도 entity 를 매개변수로 받지 않도록 수정
parkje0927 Apr 10, 2024
6509320
test: step1 - EntityJoinMetaData 수정에 따른 테스트 변경
parkje0927 Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/constants/CommonConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "_";
Comment on lines +12 to +13
Copy link
Member

Choose a reason for hiding this comment

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

상수화 👍

}
28 changes: 28 additions & 0 deletions src/main/java/docs/lecture.md
Original file line number Diff line number Diff line change
@@ -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 공부!
```

20 changes: 20 additions & 0 deletions src/main/java/docs/step1.md
Original file line number Diff line number Diff line change
@@ -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 {

}
```
69 changes: 69 additions & 0 deletions src/main/java/entity/Order.java
Original file line number Diff line number Diff line change
@@ -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<OrderItem> orderItems;

public Order() {
}

public Order(Long id, String orderNumber, List<OrderItem> 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 +
'}';
}
}
65 changes: 65 additions & 0 deletions src/main/java/entity/OrderItem.java
Original file line number Diff line number Diff line change
@@ -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 +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package persistence.context;

import dialect.Dialect;
import dialect.H2Dialect;
import persistence.entity.EntityCacheKey;
import persistence.entity.EntitySnapshot;
import pojo.FieldInfos;
Expand All @@ -13,7 +11,6 @@

public class SimplePersistenceContext implements PersistenceContext {

private final Dialect dialect = new H2Dialect();
private final Map<EntityCacheKey, Object> entitiesByKey = new HashMap<>();
private final Map<EntityCacheKey, EntitySnapshot> entitySnapshotsByKey = new HashMap<>();

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/persistence/entity/CustomJpaRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/persistence/entity/EntityEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ public interface EntityEntry {
void postRemove();

void preReadOnly();

void postReadOnly();
}
2 changes: 2 additions & 0 deletions src/main/java/persistence/entity/EntityLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ public interface EntityLoader {
<T> T findById(Class<T> clazz, Object entity, Object condition);

<T> List<T> findAll(Class<T> clazz);

<T> List<T> findByIdWithAssociation(Class<T> clazz, Object entity, Object condition);
}
22 changes: 22 additions & 0 deletions src/main/java/persistence/entity/EntityLoaderImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,4 +29,24 @@ public <T> List<T> findAll(Class<T> clazz) {
SelectQueryBuilder selectQueryBuilder = new SelectQueryBuilder(entityMetaData);
return jdbcTemplate.query(selectQueryBuilder.findAllQuery(), new RowMapperImpl<>(clazz));
}

//연관관계가 있는 경우 & eager 타입만 고려
@Override
public <T> List<T> findByIdWithAssociation(Class<T> clazz, Object entity, Object condition) {
CustomSelectQueryBuilder customSelectQueryBuilder = new CustomSelectQueryBuilder(entityMetaData);

EntityJoinMetaData entityJoinMetaData = entityMetaData.getEntityJoinMetaData();
if (!entityJoinMetaData.isLazy()) {
return eagerTypeQuery(customSelectQueryBuilder, clazz, entity);
}

//TODO 추후 lazy 타입 넣을 예정
return jdbcTemplate.query(customSelectQueryBuilder.findByIdJoinQuery(entity, clazz), new RowMapperImpl<>(clazz));

}
Copy link
Member

Choose a reason for hiding this comment

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

만드신 CustomSelectQueryBuilder 를 활용해 주셨네요 👍

몇가지 생각해 볼만한 부분들이 있어요

  1. 현재 메서드는 Test 에서만 쓰이는데, EntityManager 에서도 쓰여야 하지 않을까요?
  2. findById 류는 해당 id 를 가진 객체 하나만 return 되어야 하는데 반환타입이 List 이네요. 어떻게 하나의 객체로 만들어 반환할 수 있을까요?


private <T> List<T> eagerTypeQuery(CustomSelectQueryBuilder customSelectQueryBuilder, Class<T> clazz, Object entity) {
return jdbcTemplate.query(customSelectQueryBuilder.findByIdJoinQuery(entity, clazz), new RowMapperImpl<>(clazz));

}
}
4 changes: 2 additions & 2 deletions src/main/java/persistence/entity/EntitySnapshot.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package persistence.entity;

import pojo.FieldInfo;
import pojo.EntityColumn;
import pojo.FieldInfos;

import java.lang.reflect.Field;
Expand All @@ -17,7 +17,7 @@ public class EntitySnapshot {
public EntitySnapshot(Object entity) {
List<Field> 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()
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/persistence/sql/ddl/CreateQueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ 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(fieldInfos.getFieldDataList(), entity));
}

//create 시에 JoinColumn 어노테이션이 있는 필드는 -> JoinColumn 의 name 을 그 필드 객체 생성 시 만들어줘야 한다.
private String createClause(List<Field> fields, Object entity) {
return fields.stream()
.filter(field -> !field.isAnnotationPresent(Transient.class))
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/persistence/sql/ddl/DropQueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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());
}
}
57 changes: 57 additions & 0 deletions src/main/java/persistence/sql/dml/CustomSelectQueryBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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.getEntityJoinMetaData();
}

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(), metaDataEntityName, joinMetaDataEntityName,
metaDataEntityName + PERIOD + idField.getFieldNameData(),
joinMetaDataEntityName + PERIOD + entityJoinMetaData.getJoinColumnName(),
metaDataEntityName + PERIOD + idField.getFieldNameData(), idField.getFieldValueData());
}

private String getSelectData() {
String entityData = entityMetaData.getEntityColumns()
.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.getFieldNames()
.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;
}

}
2 changes: 1 addition & 1 deletion src/main/java/persistence/sql/dml/DeleteQueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/persistence/sql/dml/SelectQueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading